AutoMapper Usage Guidelines
Configuration
√ DO initialize AutoMapper once with Mapper.Initialize
at AppDomain startup in legacy ASP.NET
AutoMapper's static initialization is designed to build configuration once, and cache it.
√ DO use the AutoMapper.Extensions.Microsoft.DependencyInjection package in ASP.NET Core with services.AddAutoMapper(assembly[])
The extensions package will perform all the scanning and dependency injection registration. You only need to declare Profile
configuration.
X DO NOT call CreateMap
on each request
This is difficult to do now, but do not create configuration for each mapping request. Mapping configuration should be done once at startup.
X DO NOT use inline maps
Inline maps are good for very simple scenarios, but you lose the ease of configuration validation.
√ DO organize configuration into profiles
Profiles allow you to group common configuration and organize mappings by usage. This lets you put mapping configuration closer to where it's used, instead of a single file of configuration that becomes impossible to edit/maintain.
√ CONSIDER organizing profile classes close to the destination types they configure
Especially when using a vertical slice architecture, mapping configuration is easier to maintain when it's close to the destination type it configures.
X DO NOT access the static Mapper class inside a profile
If you need to get access to mapping configuration or a mapper object, all mapping methods contain overloads and all mapping extensions take a ResolutionContext
object, which includes a contextual Mapper
. This Mapper
will be contain any cached objects, services, and will be scoped appropriately.
X DO NOT use a DI container to register all profiles
AutoMapper already has configuration methods to add all profiles from assembles (AddProfiles
) and an extension to IServiceCollection
to add all profiles (services.AddAutoMapper
).
X DO NOT inject dependencies into profiles
Profiles are static configuration, and injecting dependencies into them can cause unknown behavior at runtime. If you need to use a dependency, resolve it as part of your mapping operation. You can also have your extension classes (resolvers, type converters, etc.) take dependencies directly.
√ CONSIDER using configuration options supported by LINQ over options not supported by LINQ
LINQ query extensions have the best performance of any mapping strategy, so it's better to use it as much as you can.
X AVOID before/after map configuration
If you have to do complex mapping behavior, it might be better to avoid using AutoMapper for that scenario.
X AVOID ReverseMap in cases except when mapping only top-level, non-flattened properties
Reverse mapping can get very complicated very quickly, and unless it's very simple, you can have business logic showing up in mapping configuration.
X DO NOT put any logic that is not strictly mapping behavior into configuration
AutoMapper should not perform any business logic, only mapping configuration.
X DO NOT use MapFrom when the destination member can already be auto-mapped
For example, this is not necessary:
CreateMap<Foo, FooDto>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.OrderTotal, opt => opt.MapFrom(src => src.Order.Total));
AutoMapper automaps by name, and flattens properties, so there is no need to explicitly map names that already match. Doing so results in more code than mapping manually, making the use of AutoMapper pointless.
X DO NOT use AutoMapper except in cases where the destination type is a flattened subset of properties of the source type
AutoMapper is designed for projecting a complex model into a simple one. It can be configured to map complex scenarios, but this results in more confusing code than just assigning properties directly. If your configuration is complex, don't use this tool
X DO NOT use AutoMapper to support a complex layered architecture
Please don't. Use vertical slices instead.
X AVOID using AutoMapper when you have a significant percentage of custom configuration in the form of Ignore
or MapFrom
The "Auto" is for "automatic" and if it's not "Auto" then don't use this library, it will make things more, not less complicated.
Modeling
√ DO flatten DTOs
AutoMapper can handle mapping properties Foo.Bar.Baz
into FooBarBaz
. By flattening your model, you create a more simplified object for consumers that won't require a lot of navigation to get at data.
X AVOID sharing DTOs across multiple maps
It can get confusing if you need to change a DTO and you accidentally affect another request. Model your DTOs around individual requests, and if you need to change it, you only affect that one request.
√ DO create inner types in DTOs for member types that cannot be flattened
For example:
public class OrderDto {
public List<OrderLineItemDto> LineItems { get; set; }
public class OrderLineItemDto {
}
}
This ensures that your DTOs aren't accidentally shared with other requests, resulting in undesired coupling.
X DO NOT create DTOs with circular associations
AutoMapper does support it, but it's confusing and can result in quite bad performance. Instead, create DTOs for each level of a hierarchy you want.
X AVOID changing DTO member names to control serialization
Serialization frameworks already have configuration to affect serialization, such as attributes. If member names are different, you'll need to explicitly configure member names, making the "Auto" in AutoMapper gone.
√ DO put common simple computed properties into the source model
AutoMapper supports mapping from methods if the names match or are prefixed with "Get". For example, GetFullName()
will map to FullName
.
√ DO put computed properties specific to a destination model into the destination model
Don't put a computed property on the source model if it's specific to the destination type. This will muddy the responsibility of the source model.
Execution
√ CONSIDER using query projection (ProjectTo
) over in-memory mapping
Projections are much more performant than in-memory mapping, as LINQ query providers will use the Select
expression to generate the exact SQL needed to populate your DTO directly.
X DO NOT abstract or encapsulate mapping behind an interface
It's a waste of time. Just use IMapper
directly.
√ DO use mapping options for runtime-resolved values in projections
This allows you to have parameterized projections, making sure you don't over-fetch data and filter on the client.
√ DO use mapping options for resolving contextualized services in in-memory mapping
You can use ResolutionContext.Mapper.ServiceCtor
or dependency injection directly on your resolvers, type converters, etc.
X DO NOT complain about AutoMapper if you are not following the usage guidelines
Please address all complaints to /dev/null
.