DEV Community

Cover image for Using AutoMapper with ASP.NET Core 3
Michael Jolley
Michael Jolley

Posted on • Originally published at baldbeardedbuilder.com on

Using AutoMapper with ASP.NET Core 3

AutoMapper is well known in the .NET community. It bills itself as “a simple little library built to solve a deceptively complex problem - getting rid of code that maps one object to another,” and it does the job nicely.

In the past, I’ve used it exclusively with ASP.NET APIs. However, the method for utilizing it via dependency injection has changed. So let’s review how to get started, how to define mappings and how to inject your mappings into ASP.NET Core APIs.

Getting Started

Like most .NET libraries, you can install the AutoMapper package from Nuget.

Install-Package AutoMapper
Enter fullscreen mode Exit fullscreen mode

For our purposes, we’ll focus on two classes that are related; User and UserDTO.

public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string FavoriteFood { get; set; }
    public DateTime BirthDate { get; set; }
}

public class UserDTO
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int BirthYear { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Default Mappings

These classes will services as source and destination types that we can work with. Without specific configuration, AutoMapper will match properties based on their name. By default, it will ignore null reference exceptions when mapping source and destination types. Below is a snippet mapping the source and destination types using the default configuration.

var config = new MapperConfiguration(cfg => cfg.CreateMap<User, UserDTO>());

var user = new User() 
{
    Id = Guid.NewGuid(),
    Name = "Joe Bruiser",
    FavoriteFood = "Curry",
    BirthDate = new DateTime(2000, 10, 12)
};

var mapper = config.CreateMapper();
UserDTO userDTO = mapper.Map<UserDTO>(user);
Enter fullscreen mode Exit fullscreen mode

The above will create a UserDTO object with an Id and Name that matches the original user object, but no error is thrown as a result of not having the FavoriteFood property on the UserDTO type. Also, the BirthYear property of the UserDTO will be zero.

Custom Mappings

We can use projection to translate properties as they are mapped. For instance, the code snippet below shows how we can map the BirthDate property of the User type to the BirthYear property of the UserDTO type.

var config = new MapperConfiguration(cfg =>
    cfg.CreateMap<User, UserDTO>()
        .ForMember(dest => dest.BirthYear, 
                   opt => opt.MapFrom(src => src.BirthDate.Year));

var user = new User() 
{
    Id = Guid.NewGuid(),
    Name = "Joe Bruiser",
    FavoriteFood = "Curry",
    BirthDate = new DateTime(2000, 10, 12)
};

var mapper = config.CreateMapper();
UserDTO userDTO = mapper.Map<UserDTO>(user);
Enter fullscreen mode Exit fullscreen mode

The resulting userDTO object will be similar to our first example, but this time will include the BirthYear property of 2000.

Profiles

A clean way to organize and maintain your mapping configurations is with profiles. Many times these Profile classes will encapsulate business areas (e.g. Ordering, Shipping). To start, create a class that inherits from Profile and put the configuration in the constructor.

public class UserManagementProfile : Profile
{
    public UserManagementProfile()
    {
        CreateMap<User, UserDTO>()
            .ForMember(dest => dest.BirthYear, 
            opt => opt.MapFrom(src => src.BirthDate.Year));

        // Configurations for other classes in this business 
        // area can be included here as well, like below:

        // CreateMap<Role, RoleDTO>();
        // CreateMap<Permission, PermissionDTO>();
    }
}
Enter fullscreen mode Exit fullscreen mode

For added isolation, you could create a project just for your Profile configurations. Using profiles helps keeps your configurations more manageable as your application grows.

Dependency Injection

Dependency injection is baked into ASP.NET Core, but to use AutoMapper with it you’ll need additional configuration and an additional Nuget package. Add the

AutoMapper.Extensions.Microsoft.DependencyInjection to your ASP.NET Core project.

Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
Enter fullscreen mode Exit fullscreen mode

Register AutoMapper

Once installed, you can define the configuration using profiles. In the Startup.ConfigureServices method, use can use the AddAutoMapper extension method on the IServiceCollection object as shown below:

// By Marker
services.AddAutoMapper(typeof(ProfileTypeFromAssembly1) /*, ...*/);

// or by Assembly
services.AddAutoMapper(profileAssembly1, profileAssembly2 /*, ...*/);
Enter fullscreen mode Exit fullscreen mode

Inject AutoMapper

With AutoMapper registered and its configurations set, you can now inject an IMapper into your controllers.

public class UsersController
{
    private readonly IMapper _mapper;

    public UsersController(IMapper mapper) => _mapper = mapper;

    // use _mapper.Map
}
Enter fullscreen mode Exit fullscreen mode

With the IMapper you can now map your objects to their DTO equivalents using the .Map method.

Wrap It Up

Now that ASP.NET Core is injecting AutoMapper to your controllers, you can add configurations to your profiles or create new profiles for new business areas and still map appropriately without further configuration.

Of course, we didn’t cover all of the features of AutoMapper so I’d suggest checking out their documentation for more information about their capabilities. Hopefully this post gave you enough information to start trying AutoMapper yourself. Let me know in the comments how you use AutoMapper in your applications.

Top comments (3)

Collapse
 
theonlybeardedbeast profile image
TheOnlyBeardedBeast • Edited

What I miss in most of the tutorials for automapper is working with lists, so with that basic mapping, we are able to map a list of users to a list of usersDTO as well.

mapper.Map<List<UserDTO>>(users); // where variable users has List<User> type
Collapse
 
michaeljolley profile image
Michael Jolley

Oh yeah! All the basic collection types are supported so you can do anything like:

IEnumerable<Destination> ienumerableDest = mapper.Map<Source[], IEnumerable<Destination>>(sources);
ICollection<Destination> icollectionDest = mapper.Map<Source[], ICollection<Destination>>(sources);
IList<Destination> ilistDest = mapper.Map<Source[], IList<Destination>>(sources);
List<Destination> listDest = mapper.Map<Source[], List<Destination>>(sources);
Destination[] arrayDest = mapper.Map<Source[], Destination[]>(sources);

But in your example where the list is a property on another mapped object you might need one other thing. If that property could be null, you’ll want to add something to your configuration:

var configuration = new MapperConfiguration(cfg => {
    cfg.AllowNullCollections = true;
    cfg.CreateMap<Source, Destination>();
});

The AllowNullCollections will prevent null exception errors on collections. By default, AutoMapper behaves like Entity Framework in that it believes collection properties should never be null.

Collapse
 
theonlybeardedbeast profile image
TheOnlyBeardedBeast

I like to always initialize my collections, but thanks for this information.