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.
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:
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 withservices.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.
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 theGet
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:
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.