Configuration
Table of contents
MapperConfiguration- Registering Maps
- Adding Profiles
- Creating a Mapper
- Configuration Validation
IMapperConfigurationExpressionOptions- Configuration Patterns
- Thread Safety
- Common Pitfalls
MapperConfiguration
MapperConfiguration is the entry point for EggMapper. You construct it once (typically at startup) and keep a single instance for the lifetime of the application.
using EggMapper;
var config = new MapperConfiguration(cfg =>
{
// Register maps here
cfg.CreateMap<Source, Destination>();
});
MapperConfiguration is immutable after construction. It is safe to call CreateMapper(), Map(), and BuildProjection() concurrently from any number of threads.
Registering Maps
Basic map
cfg.CreateMap<Source, Destination>();
By convention, properties with identical names (case-insensitive) are mapped automatically. No configuration needed for matching property names.
With custom member mapping
cfg.CreateMap<Order, OrderDto>()
.ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FullName))
.ForMember(d => d.TotalWithTax, o => o.MapFrom(s => s.Total * 1.1m))
.ForMember(d => d.InternalNotes, o => o.Ignore());
Reverse map
cfg.CreateMap<Source, Destination>().ReverseMap();
// Registers both Source -> Destination and Destination -> Source
Multiple maps
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, CustomerSummaryDto>();
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<Order, OrderDto>();
cfg.CreateMap<Order, OrderSummaryDto>();
Open generic maps
cfg.CreateMap(typeof(Result<>), typeof(ResultDto<>));
cfg.CreateMap(typeof(PagedList<>), typeof(PagedListDto<>));
Adding Profiles
Group related maps in a Profile class and register the entire profile at once:
cfg.AddProfile<OrderProfile>();
cfg.AddProfile<CustomerProfile>();
Or scan an assembly for all profiles:
cfg.AddProfiles(typeof(OrderProfile).Assembly);
Assembly scanning is recommended for large projects. It automatically discovers and registers all Profile subclasses.
Creating a Mapper
IMapper mapper = config.CreateMapper();
CreateMapper() returns a lightweight IMapper instance backed by the compiled delegate cache. You may call it multiple times; each call returns a new wrapper around the same shared cache.
Configuration Validation
Validate that every destination property has a source mapping (catches typos and missing maps early):
config.AssertConfigurationIsValid();
Call this in unit tests or at application startup to surface misconfiguration immediately.
// Throws InvalidOperationException if any destination property is unmapped
config.AssertConfigurationIsValid();
What validation checks
- Every destination property must have either:
- A matching source property (by name, case-insensitive)
- A custom
MapFromexpression - A flattening match (e.g.,
AddressCitymatchesAddress.City) - An explicit
Ignore()call
- Nested type maps must be registered
- Constructor parameters must match source properties
Recommended: validate in a unit test
[Fact]
public void AllMappings_ShouldBeValid()
{
var config = new MapperConfiguration(cfg =>
cfg.AddProfiles(typeof(OrderProfile).Assembly));
config.AssertConfigurationIsValid();
}
IMapperConfigurationExpression Options
| Method | Description |
|---|---|
CreateMap<TSrc, TDst>() | Register a new type map and return the fluent builder |
CreateMap(Type src, Type dst) | Register an open-generic type map |
AddProfile<TProfile>() | Register all maps declared in a Profile subclass |
AddProfile(profile) | Register a pre-constructed Profile instance |
AddProfiles(assemblies) | Scan assemblies and register all Profile subclasses |
Configuration Patterns
Small application — inline configuration
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Product, ProductDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FullName));
});
Medium application — profiles
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<OrderProfile>();
cfg.AddProfile<CustomerProfile>();
cfg.AddProfile<ProductProfile>();
});
Large application — assembly scanning
var config = new MapperConfiguration(cfg =>
cfg.AddProfiles(
typeof(OrderProfile).Assembly, // Core domain
typeof(ReportProfile).Assembly)); // Reporting module
Multi-module application
// Each module defines its own profiles
builder.Services.AddEggMapper(
typeof(OrderModule.OrderProfile).Assembly,
typeof(InventoryModule.ProductProfile).Assembly,
typeof(ReportingModule.ReportProfile).Assembly);
Thread Safety
| Scenario | Safe? |
|---|---|
Construct MapperConfiguration on one thread | Yes |
Call CreateMapper() concurrently | Yes |
Call IMapper.Map() concurrently | Yes |
Call IMapper.MapList() concurrently | Yes |
Call IMapper.Patch() concurrently | Yes |
Use ProjectTo() concurrently | Yes |
| Add maps after construction | Not supported |
All mapping operations are thread-safe after construction. The compiled delegate cache is immutable and shared across all mapper instances.
Common Pitfalls
- Constructing
MapperConfigurationper request — This recompiles all expression trees every time. Always keep it as a singleton. - Adding maps after construction — Not supported. All maps must be registered in the constructor callback or in profiles.
- Not calling
AssertConfigurationIsValid()— Missing maps or typos in property names silently leave destination properties at their default values. Always validate in tests. - Registering the same type pair twice — The second
CreateMap<S,D>()overwrites the first. This is usually a mistake. Keep each type pair in a single profile.