Auto Mapper in .NET 6

2022, May 18

AutoMapper is a library created by Jimmy Bogard and it was created to offer object-object mapping, by transforming an input object of a type onto an output object of a different type.

What are the benefits of Auto Mapper?

  • Simple mapping between objects - it automatically maps properties between objects.
  • Simple configuration - it is very straightforward to configure it.
  • Multiple features - in the context of complex mapping, it has many features to help solve the mapping between objects.

Mapping

Nuget Packages

In this example, we use AutoMapper and AutoMapper.Extensions.Microsoft.DependencyInjection, as we leverage Dependency Injection.

Sample

The solution is an example of some features available with AutoMapper. For a more comprehensive explanation of all the features, check the official documentation.

Solution

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAutoMapper(typeof(Program));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Because we are using Dependency Injection, the line that hooks AutoMapper to the Service Collection is this: builder.Services.AddAutoMapper(typeof(Program));.

AutoMapper is injected through dependency injection, which is exemplified on the controller UserController.cs:

public UserController(ILogger<UserController> logger, IMapper mapper)
{
    _logger = logger;
    _mapper = mapper;
}

On UserProfile.cs the mapping between objects is specified:

UserProfile.cs

using AutoMapper;
using AutoMapperDemo.Models;
using AutoMapperDemo.ViewModel;

namespace AutoMapperDemo
{
    public class UserProfile : Profile
    {
        public UserProfile()
        {
            CreateMap<User, UserViewModel>();

            CreateMap<User, UserNameViewModel>()
                .ForMember(dest => dest.FName, opt => opt.MapFrom(src => src.FirstName))
                .ForMember(dest => dest.LName, opt => opt.MapFrom(src => src.LastName))
                .ForMember(dest => dest.Description, opt => opt.Ignore())
                .AfterMap((_, dest) =>
                {
                    dest.DateCreated = DateTime.Now;
                })
                .ReverseMap();

            CreateMap<UserAddress, UserAddressViewModel>();
        }
    }
}

Let's explore some AutoMapper features:

Simple Mapping

In this mapping example, AutoMapper automatically handles the mapping, with minimal configuration. Let's consider these 2 models:

User.cs (Source)

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Address { get; set; }
}

UserViewModel.cs (Destination)

public class UserViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

Looking at UserProfile.cs, the line that does the mapping is this CreateMap<User, UserViewModel>();, which does the mapping from User to UserViewModel.

In this example the API implements this conversion:

[HttpGet("GetUsers")]
public IActionResult GetUsers()
{
    var users = GetUserList();

    var usersViewModel = _mapper.Map<List<UserViewModel>>(users);

    return Ok(usersViewModel);
}

ForMember, ReverseMap, Ignore and AfterMap Method Mapping

In this mapping example, AutoMapper is used for mapping objects with different property labels, and with that, we need to tell AutoMapper how to work.

The ForMember method allows the specific property mapping from a source and destination. e.g.: .ForMember(dest => dest.FName, opt => opt.MapFrom(src => src.FirstName)) where the field FirstName is mapped onto FName.

The ReverseMap method does the automatic reverse mapping between objects. e.g.: On this mapping CreateMap<User, UserNameViewModel>(), by having this CreateMap<User, UserNameViewModel>().ReverseMap() is the same as CreateMap<UserNameViewModel, User>().

The Ignore method ignores the mapping of an specific property. e.g.: .ForMember(dest => dest.Description, opt => opt.Ignore())

The AfterMap allows for any changes to the objects to happen after the mapping occurs. e.g.: CreateMap<User, UserNameViewModel>().AfterMap((dest, src) => { dest.DateCreated = DateTime.Now;});

Flattening Complex Mapping

In cases where we have complex types, AutoMapper helps us flatten objects to a simpler model.

UserAddress.cs (Source)

public class UserAddress
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Address Address { get; set; }

    public string GetFullName => $"{FirstName}, {LastName}";
}

Address.cs (Source)

public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

UserAddressViewModel.cs (Destination)

public class UserAddressViewModel
{
    public string FullName { get; set; }
    public string AddressCountry { get; set; }
    public string Email { get; set; }
}

Looking at UserProfile.cs, the line that does the mapping is this CreateMap<UserAddress, UserAddressViewModel>();, which does the mapping from UserAddress to UserAddressViewModel.

This API implements this conversion:

[HttpGet("GetUserAddress")]
public IActionResult GetUserAddress()
{
    var users = GetUserAddressList();

    var userAddressesViewModel = _mapper.Map<List<UserAddressViewModel>>(users);

    return Ok(userAddressesViewModel);
}

Queryable Extension Mapping

The AutoMapper queryable extension can be applied to IQueryable queries, and that allows for complex objects to be flattened. This is done by using the ProjectTo method:

[HttpGet("GetUsersProjectTo")]
public IActionResult GetUsersProjectTo()
{
    var users = GetUserList();

    var usersViewModel = users
        .AsQueryable()
        .ProjectTo<UserViewModel>(_mapper.ConfigurationProvider)
        .ToList();

    return Ok(usersViewModel);
}

The .ProjectTo<UserViewModel> tells AutoMapper's mapping engine to emit a select clause to the IQueryable, the same as if you manually projected your IQueryable to an UserViewModel with a Select clause.

Check this sample in the PlayGoKids repository