In this article, we are going to learn how to use AutoMapper in an ASP.NET Core application.

We are going to start by looking into what AutoMapper is and what problem it solves. Then, we are going to explain how we can use AutoMapper in our MVC application. After that, we’ll learn about the usage guidelines and best practices. We’ll also take a look at what’s happening behind the scenes and how to flatten complex object models.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
You can find the source code on our GitHub repository.

If you prefer video learning, you can watch the video on the same topic:


VIDEO: Getting Started with AutoMapper in ASP.NET Core video.


Let’s learn what problems we can solve with AutoMapper.

What is AutoMapper?

AutoMapper is a simple library that helps us to transform one object type into another. It is a convention-based object-to-object mapper that requires very little configuration. 

The object-to-object mapping works by transforming an input object of one type into an output object of a different type.

One Use Case

AutoMapper was built to solve a complex problem that most developers face in their day-to-day life – writing code that maps one object type to another. This type of code is rather tedious and boring to write, so why not leave that job to this little tool?

What makes AutoMapper interesting is that it provides some easy-to-use conventions to take the dirty work out of figuring out how to map Type A to Type B. As long as Type B follows AutoMapper’s established conventions, almost no configuration is needed to map two types.

Here’s one common scenario. We’ve created an application and we want to keep the separation between our domain models and our view models. 

In order to accomplish this, we need to write the code to adapt our domain model to our view model. Then, as we add more views and domain models, we end up writing more adapters. Later on, we’ll have to write even more adapters to map our data transfer objects from the database layer into our domain objects. 

This is mundane and repetitive. And this is where AutoMapper comes in.

How to Use AutoMapper in Our Application

Let’s have a look at how to add Automapper into our .NET Core application.

Installation

The first step is to install the corresponding NuGet package:

Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

If we install the AutoMapper.Extensions.Microsoft.DependencyInjection package, it will automatically install the AutoMapper package for us since it references it.

Configuration

We’ll explain the configuration for both .NET 5, and .NET 6 and above versions.

After installing the required package, the next step is to configure the services. Let’s do it in the Startup.cs class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAutoMapper(typeof(Startup));
    services.AddControllersWithViews();
}

For .NET 6, we have to modify the Program class:

builder.Services.AddAutoMapper(typeof(Program)); 
builder.Services.AddControllersWithViews();

That’s it. AutoMapper is installed and configured in our project. Now, let’s see how to use it with our objects.

Usage

Let’s say we have a domain object named User:

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; }
}

In the UI layer, we would have a ViewModel to display the user information:

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

Now, let’s see how we are going to convert our domain object to a view model.

Profiles

A good way to organize our mapping configurations is with Profiles. We need to create classes that inherit from Profile class and put the configuration in the constructor:

public UserProfile()
{
    CreateMap<User, UserViewModel>();
}

UserProfile class creates the mapping between our User domain object and UserViewModel. As soon as our application starts and initializes AutoMapper, AutoMapper will scan our application and look for classes that inherit from the Profile class and load their mapping configurations.

Now, let’s define a Controller and use the Auto-Mapping capabilities that we just added:

public class UserController : Controller
{
    private readonly IMapper _mapper;

    public UserController(IMapper mapper)
    {
        _mapper = mapper;
    }

    public IActionResult Index()
    {
        // Populate the user details from DB
        var user = GetUserDetails();

        UserViewModel userViewModel = _mapper.Map<UserViewModel>(user);

        return View(userViewModel);
    }
}

First, we inject the mapper object into the controller. Then, we call the Map() method, which maps the User object to the UserViewModel object. Furthermore, pay attention to a local method GetUserDetails that we use for the local data storage. You can find its implementation in our source code.

Let’s create a View for the Index action method as explained in the article’s section: Creating Views.

Now let’s run the application:

run the app

We can see our User domain object is properly mapped to the UserViewModel object.

Creating Rules for Mapping Properties With Different Names

Well, that was quite simple, wasn’t it? 

But what if we have different property names in our source and destination objects. Let’s take a look at how to do the mapping in these cases.

Let’s modify the property names in UserViewModel class:

public class UserViewModel
{
    public string FName { get; set; }
    public string LName { get; set; }
    public string Email { get; set; }
}

Here we need to map the properties from the User domain object to our UserViewModel:

User.FirstName -> UserViewModel.FName
User.LastName -> UserViewModel.LName
User.EMail -> UserViewModel.Email

So, let’s change the mapping in the UserProfile class:

public UserProfile()
{
    CreateMap<User, UserViewModel>()
        .ForMember(dest =>
            dest.FName,
            opt => opt.MapFrom(src => src.FirstName))
        .ForMember(dest =>
            dest.LName,
            opt => opt.MapFrom(src => src.LastName))
}

We use the CreateMap() method to create a mapping by providing the source and destination properties. 

If we want to customize the configuration for individual members, we can use the ForMember() method which has the parameters destinationMember, which is of type Expression and memberOptions, which is of type Action.

For example, the above code maps FirstName and LastName properties of the User object to the FName and the LName property of UserViewModel respectively.

After making these changes, let’s run the application once again. We can see that these properties are mapped correctly.

Reverse Mapping

So far, we have only looked at one-directional mapping, which means if we have two types Type A and Type B, then we only map Type A to Type B. But, by using Automapper’s Reverse mapping capability, it is possible to achieve bi-directional mapping:

public UserProfile()
{
    CreateMap<User, UserViewModel>()
        .ForMember(dest =>
            dest.FName,
            opt => opt.MapFrom(src => src.FirstName))
        .ForMember(dest =>
            dest.LName,
            opt => opt.MapFrom(src => src.LastName))
        .ReverseMap();
}

Once Reverse Mapping is configured, we can map back from destination to source type:

var mappedUser = _mapper.Map<User>(userViewModel);

This way, we can easily achieve bi-directional mapping between types using AutoMapper’s Reverse Mapping capabilities. 

Just one note. We can use AutoMapper in the same manner in the ASP.NET Core Web API project as well, the implementation is the same just we wouldn’t map to the view models but, for example, our DTO classes.

Behind the Scenes

We have seen the magic of AutoMapper in action. So what happens in the background? 

AutoMapper uses a programming concept called Reflection to retrieve the type metadata of objects. We can use reflection to dynamically get the type from an existing object and invoke its methods or access its fields and properties. 

Then, based on the conventions and configurations defined, we can easily map the properties of the two types. AutoMapper was built around this concept.

Usage Guidelines and Best practices

As with every other component that we use in our application, there are certain usage guidelines and best practices that we need to follow while using AutoMapper.

Do’s 

  • Always use the AutoMapper.Extensions.Microsoft.DependencyInjection package in ASP.NET Core with services.AddAutoMapper(assembly[]). This package will perform all the scanning and dependency injection registration. We only need to declare the Profile configurations.
  • Always organize configuration into Profiles. Profiles allow us to group a common configuration and organize mappings by usage. This lets us put mapping configuration closer to where it is used, instead of a single file of configuration that becomes difficult to edit/maintain.
  • Always use configuration options supported by LINQ over their counterparts as LINQ query extensions have the best performance of any mapping strategy.
  • Always flatten DTOs. AutoMapper can handle mapping properties A.B.C into ABC. By flattening our model, we create a more simplified object that won’t require a lot of navigation to get at data.
  • Always put common simple computed properties into the source model. Similarly, we need to place computed properties specific to the destination model in the destination model.

Don’ts

  • Do not call CreateMap() on each request. It is not a good practice to create the configuration for each mapping request. Mapping configuration should be done once at startup.
  • Do not use inline maps. Inline maps may seem easier for very simple scenarios, but we lose the ease of configuration.
  • If we have to write a complex mapping behavior, it might be better to avoid using AutoMapper for that scenario.
  • Do not put any logic that is not strictly mapping behavior into the configuration. AutoMapper should not perform any business logic, it should only handle the mapping.
  • Avoid sharing DTOs across multiple maps. Model your DTOs around individual actions, and if you need to change it, you only affect that action.
  • Do not create DTOs with circular associations. AutoMapper does support it, but it’s confusing and can result in quite bad performance. Instead, we can create separate DTOs for each level of the hierarchy we want.
To move on from basics and learn how to implement advanced mappings with AutoMapper, we recommend reading the IncludeMembers and CustomProjections with AutoMapper article. There, you will learn how to utilize AutoMapper in a more advanced fashion.

Flattening Complex Object Models

AutoMapper supports flattening complex object models into DTO or another simple object model. For example, Domain Objects usually have a complex object model with many associations between them, but ViewModels generally have a flat object model. 

We can map Domain Objects to ViewModels with AutoMapper’s Flattening

If we follow proper naming conventions for our object models, then there is no need to provide any additional configuration code. AutoMapper works with conventions and maps our object model from complex to flat/simple ones.

AutoMapper uses the following conventions:

  • It will automatically map properties with the same names.
  • If the source object has some association with other objects, then it will try to map with properties on the destination object whose name is a combination of the source class name and property name in the Pascal case
  • It will try to map methods on the source object which has a Get prefix with a property on the destination object with the name excluding the Get prefix.

If we follow these conventions, AutoMapper will automatically map our objects. Otherwise, we’ll need to configure AutoMapper using Fluent API.

Let’s modify our User object by adding a child object Address:

public class User
{
    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()
    {
        return $"{this.LastName}, {this.FirstName}";
    }
}

And here’s how the Address class looks like:

public class Address
{
    public int Id { get; set; }
    public string Door { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }
    public string ZipCode { get; set; }
}

Also, note that we have added a method GetFullName() to get the user’s full name.

Let’s modify the UserViewModel class:

public class UserViewModel
{
    [Display(Name = "Full Name")]
    public string FullName { get; set; }

    [Display(Name = "Country")]
    public string AddressCountry { get; set; }

    public string Email { get; set; }
}

Now, Let’s modify the profile class to use the default conventions:

public UserProfile()
{
    CreateMap<User, UserViewModel>();
}

Let’s run the application once again:

run the app with complex type mapping

This time, we can see that the GetFullName() method on the source object is correctly mapped to FullName property on the destination object.

Similarly, User.Address.Country property is automatically mapped to UserViewModel.AddressCountry

These mappings are correctly handled by the AutoMapper using its default conventions.

Conclusion

In this article, we have learned the following concepts:

  • The AutoMapper component – what is it and when to use it?
  • Installing, Configuring, and using the AutoMapper component in our ASP.NET Core application
  • How AutoMapper works behind the scenes
  • Usage Guidelines and best practices while using AutoMapper.
  • Flattening a complex object model using AutoMapper’s default conventions.

That’s all for now. Hope you enjoyed the article. We’ll cover more advanced topics related to AutoMapper in one of the following articles.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!