Convention-Based Object Mapping and Configuration Strategies in AutoMapper
Naming Conventions and Automatic Flattening
AutoMapper operates on a convantion-driven approach. For seamless translation between two objects, the source and destination properties must share identical names and compatible data types. When dealing with nested object graphs, the library supports automatic flattening. If a source entity contains a complex navigation property, the destination DTO should name the corresponding fields by concatenating the navigation property name with the inner member name.
public class Employee
{
public int EmployeeId { get; set; }
public string FullName { get; set; }
public Department AssignedDepartment { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Title { get; set; }
}
public class EmployeeDto
{
public int EmployeeId { get; set; }
public string FullName { get; set; }
// Flattened properties follow the {NavigationProperty}{InnerProperty} pattern
public int AssignedDepartmentId { get; set; }
public string AssignedDepartmentTitle { get; set; }
}
Mapping Registration
Translation rules are defined during application initialization. A configuration object establishes how source types project onto destination types.
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>();
});
IMapper mapper = configuration.CreateMapper();
Bidirectional Synchronization
To enable two-way data flow, chain the ReverseMap() directive to the base configuration. This allows modifications applied to the destination object to be projected back onto the original source entity.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>().ReverseMap();
});
var mapper = config.CreateMapper();
var sourceEntity = new Employee { EmployeeId = 1, FullName = "Alice" };
var projectedDto = mapper.Map<EmployeeDto>(sourceEntity);
projectedDto.FullName = "Robert"; // Modify the DTO
mapper.Map(projectedDto, sourceEntity); // Sync changes back to the source
Configuration Validation and Member Ignorance
By default, the engine verifies that every destination member has a matching source counterpart. Calling AssertConfigurationIsValid() during startup will throw an exception if unresolved mappings are detected. To bypass validation for specific fields, utilize the ForMember API with the Ignore() option.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>()
.ForMember(dest => dest.AssignedDepartmentTitle, opt => opt.Ignore());
});
config.AssertConfigurationIsValid();
Validation scope can be adjusted per mapping profile. You can restrict checks to source members, destination members, or disable them entirely using the MemberList enumeration.
cfg.CreateMap<Employee, EmployeeDto>(MemberList.Destination); // Only validate DTO properties
// Use MemberList.None to bypass validation for both sides
Collection Handling
Once a single-object mapping rule is registered, the engine automatically processes enumerables, lists, and arrays without requiring additional configuration.
IEnumerable<Employee> staffList = GetEmployees();
IList<EmployeeDto> staffDtos = mapper.Map<IList<EmployeeDto>>(staffList);
Polymorphic and Inheritance Mapping
Translating derived types requires explicit hierarchy registration. You must declare the inheritance relationship in the base map and configure the derived type mapping separately.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>()
.Include<Manager, ManagerDto>(); // Link base to derived type
cfg.CreateMap<Manager, ManagerDto>(); // Register derived mapping
});
Employee[] personnel = new Employee[]
{
new Employee(),
new Manager()
};
EmployeeDto[] mappedPersonnel = mapper.Map<EmployeeDto[]>(personnel);
// Base instances translate to EmployeeDto
// Derived instances translate to ManagerDto
The Include directive establishes the polymorphic chain, while the seconadry CreateMap handles the specific derived type translation rules.