Seamless AutoMapper integration for SimpleInjector with automatic registration and configuration
This library eliminates the boilerplate of manually registering AutoMapper with SimpleInjector. It automatically:
- 🔍 Scans assemblies for
Profileclasses and registers them - 🔄 Auto-registers all AutoMapper extensions (
ITypeConverter,IValueResolver,IMemberValueResolver,IValueConverter,IDestinationFactory,ICondition,IPreCondition,IMappingAction) - 💉 Handles dependency injection for your custom converters and resolvers
- ⚙️ Configures everything with a single fluent API call
- 🎯 Supports .NET 8, 9, and 10
dotnet add package AdaskoTheBeAsT.AutoMapper.SimpleInjectorpublic class UserProfile : Profile
{
public UserProfile()
{
CreateMap<UserEntity, UserDto>();
CreateMap<UserDto, UserEntity>();
}
}using AdaskoTheBeAsT.AutoMapper.SimpleInjector;
using SimpleInjector;
var container = new Container();
// Option 1: Scan by marker type (recommended)
container.AddAutoMapper(typeof(UserProfile));
// Option 2: Scan multiple assemblies
container.AddAutoMapper(typeof(UserProfile).Assembly, typeof(OrderProfile).Assembly);
// Option 3: Scan with configuration
container.AddAutoMapper(cfg =>
{
cfg.WithMapperAssemblyMarkerTypes(typeof(UserProfile));
cfg.AsScoped(); // Change lifestyle if needed
});public class UserService
{
private readonly IMapper _mapper;
public UserService(IMapper mapper)
{
_mapper = mapper;
}
public UserDto GetUser(UserEntity entity)
{
return _mapper.Map<UserDto>(entity);
}
}This library automatically registers the following with your container:
| Type | Registration | Description |
|---|---|---|
IConfigurationProvider |
Singleton | AutoMapper configuration (not registered when using custom mapper instance) |
IMapper |
Singleton* | Main mapper instance |
ITypeConverter<,> |
Transient | Custom type converters |
IValueConverter<,> |
Transient | Custom value converters |
IValueResolver<,,> |
Transient | Custom value resolvers |
IMemberValueResolver<,,,> |
Transient | Custom member resolvers |
IDestinationFactory<,> |
Transient | Custom destination object factories (ConstructUsing<T>()) |
ICondition<,,> |
Transient | Custom member conditions (Condition<T>()) |
IPreCondition<,> |
Transient | Custom member pre-conditions (PreCondition<T>()) |
IMappingAction<,> |
Transient | Custom mapping actions |
*Lifestyle is configurable (Singleton/Scoped/Transient)
container.AddAutoMapper(typeof(UserProfile), typeof(OrderProfile));public static class AutoMapperConfigurator
{
private const string NamespacePrefix = "YourCompany.YourProject";
public static void Configure(Container container)
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var assemblies = new List<Assembly>();
var mainAssembly = typeof(AutoMapperConfigurator).Assembly;
var refAssemblies = mainAssembly.GetReferencedAssemblies();
foreach (var assemblyName in refAssemblies
.Where(a => a.FullName.StartsWith(NamespacePrefix, StringComparison.OrdinalIgnoreCase)))
{
var assembly = loadedAssemblies.Find(l => l.FullName == assemblyName.FullName)
?? AppDomain.CurrentDomain.Load(assemblyName);
assemblies.Add(assembly);
}
container.AddAutoMapper(assemblies);
}
}Your custom resolvers can have dependencies automatically injected:
public class DependencyResolver : IValueResolver<Source, Dest, int>
{
private readonly ISomeService _service;
public DependencyResolver(ISomeService service)
{
_service = service;
}
public int Resolve(Source source, Dest dest, int destMember, ResolutionContext context)
{
return _service.Modify(destMember);
}
}
// Registration
container.Register<ISomeService, SomeService>();
container.AddAutoMapper(typeof(DependencyResolver));
// The resolver will automatically get ISomeService injected!container.AddAutoMapper(cfg =>
{
cfg.WithMapperAssemblyMarkerTypes(typeof(UserProfile));
cfg.AsScoped(); // Use Scoped lifestyle
// or cfg.AsTransient(); // Use Transient lifestyle
// or cfg.AsSingleton(); // Default: Singleton
});Add custom mappings programmatically:
container.AddAutoMapper(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.WithMapperConfigurationExpressionAction((container, expression) =>
{
// Add custom mappings
expression.CreateMap<Foo, Bar>().ReverseMap();
// Configure global settings
expression.AllowNullCollections = true;
});
});public class MyCustomMapper : IMapper
{
// Your custom implementation
}
container.AddAutoMapper(cfg =>
{
cfg.Using<MyCustomMapper>();
cfg.WithMapperAssemblyMarkerTypes(typeof(UserProfile));
});Perfect for unit tests using Moq or similar frameworks.
When using a custom mapper instance via Using(Func<IMapper>), IConfigurationProvider is not registered
in the container, since the custom mapper fully replaces the default AutoMapper pipeline:
var testMapper = new Mock<IMapper>();
testMapper.Setup(m => m.Map<UserDto>(It.IsAny<UserEntity>()))
.Returns(new UserDto { Id = 1, Name = "Test User" });
container.AddAutoMapper(cfg =>
{
cfg.Using(() => testMapper.Object);
cfg.WithMapperAssemblyMarkerTypes(typeof(UserProfile));
});If you're using AutoMapper's premium features:
container.AddAutoMapper(cfg =>
{
cfg.WithLicenseKey("your-license-key");
cfg.WithMapperAssemblyMarkerTypes(typeof(UserProfile));
});Inject IMapper into your services to perform runtime mapping:
public class EmployeesController
{
private readonly IMapper _mapper;
private readonly IEmployeeRepository _repository;
public EmployeesController(IMapper mapper, IEmployeeRepository repository)
{
_mapper = mapper;
_repository = repository;
}
public EmployeeDto GetEmployee(int id)
{
var employee = _repository.GetById(id);
return _mapper.Map<EmployeeDto>(employee);
}
public IEnumerable<EmployeeDto> GetAllEmployees()
{
var employees = _repository.GetAll();
return _mapper.Map<IEnumerable<EmployeeDto>>(employees);
}
}For efficient database queries, use ProjectTo to project directly to DTOs:
public class OrderService
{
private readonly DbContext _dbContext;
private readonly IConfigurationProvider _configurationProvider;
public OrderService(DbContext dbContext, IConfigurationProvider configurationProvider)
{
_dbContext = dbContext;
_configurationProvider = configurationProvider;
}
// Option 1: Using IConfigurationProvider directly (recommended)
public async Task<List<OrderDto>> GetOrdersAsync()
{
return await _dbContext.Orders
.ProjectTo<OrderDto>(_configurationProvider)
.ToListAsync();
}
}Or inject IMapper instead:
public class OrderService
{
private readonly DbContext _dbContext;
private readonly IMapper _mapper;
public OrderService(DbContext dbContext, IMapper mapper)
{
_dbContext = dbContext;
_mapper = mapper;
}
// Option 2: Using IMapper.ConfigurationProvider
public async Task<List<OrderDto>> GetOrdersAsync()
{
return await _dbContext.Orders
.ProjectTo<OrderDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
}| Method | Description |
|---|---|
AddAutoMapper(params Type[]) |
Register AutoMapper by scanning assemblies containing the specified types |
AddAutoMapper(params Assembly[]) |
Register AutoMapper by scanning the specified assemblies |
AddAutoMapper(IEnumerable<Assembly>) |
Register AutoMapper by scanning the specified assemblies |
AddAutoMapper(IEnumerable<Type>) |
Register AutoMapper by scanning assemblies containing the specified types |
AddAutoMapper(Action<AutoMapperSimpleInjectorConfiguration>) |
Register AutoMapper with custom configuration |
| Method | Description |
|---|---|
WithMapperAssemblyMarkerTypes(params Type[]) |
Specify types to mark assemblies for scanning (eagerly materialized, throws on null) |
WithAssembliesToScan(IEnumerable<Assembly>) |
Specify assemblies to scan directly (defensively copied, throws on null) |
Using<TMapper>() |
Use a custom IMapper implementation |
Using(Func<IMapper>) |
Use a factory function to create IMapper (useful for testing; skips IConfigurationProvider registration) |
AsSingleton() |
Register IMapper as Singleton (default) |
AsScoped() |
Register IMapper as Scoped |
AsTransient() |
Register IMapper as Transient |
WithMapperConfigurationExpressionAction(...) |
Add custom mapper configuration |
WithLicenseKey(string) |
Set AutoMapper license key (only applied when non-null/non-empty) |
- .NET 8.0, 9.0, or 10.0
- AutoMapper 15.1.0+
- SimpleInjector 5.5.0+
dotnet testThe test suite needs an AutoMapper license key. AutoMapper resolves it from the environment
(AUTOMAPPER_LICENSE_KEY, then the shared LUCKYPENNY_LICENSE_KEY) unless a key is passed
explicitly via WithLicenseKey(...). In CI the value comes from the AUTOMAPPER_LICENSE_KEY
GitHub secret (see .github/workflows/ci.yml).
To supply it locally, copy the template and fill in your key:
cp .env.example .envAUTOMAPPER_LICENSE_KEY=your-license-key.env is git ignored, so the key never lands in the repository. A module initializer in the test
project (TestEnvironment) uses dotenv.net to probe
upwards from the test output directory for the nearest .env and exports every KEY=VALUE entry
into the test process before the first MapperConfiguration is built. Variables that are already
set in the environment are never overwritten, so CI values always win over the file.
This works the same for dotnet test and for the test runners in Visual Studio, Rider, and VS Code.
If you prefer not to keep a file in the working tree, set a user-level environment variable instead
(setx AUTOMAPPER_LICENSE_KEY "your-license-key" on Windows) and restart your IDE or shell.
Special thanks to:
- Jimmy Bogard for creating AutoMapper
- Steven van Deursen for creating SimpleInjector
This library is based on AutoMapper.Extensions.Microsoft.DependencyInjection and adapted to work seamlessly with SimpleInjector.
Found this helpful? Give it a ⭐ on GitHub!