Configuring AutoMapper for use with the ASP.NET Core DI Container

I’m generally a strong proponent of using separate models for commmunicating across service boundaries that are separate and distinct from my core domain model. In a typical web application that is backed by a database, this usually means that I have a domain model, a storage model, and a view model, and I map between storage model ↔ domain model and between domain model ↔ view model when crossing their respective boundaries. This keeps my domain model isolated, ensures that it is not forced to change due to concerns in another layer, and promotes adherence to the Single Responsibility Principle (a class should have one, and only one, reason to change).

Having multiple models implies that there is some mechanism for mapping between models. My go-to tool for this task is AutoMapper. Configuring AutoMapper for use in an ASP.NET Core project using the ASP.NET Core DI container differs slightly from my prior experiences with it on past projects. Here’s what I did to get up and running…

Install Nuget Packages

Let’s first install the Nuget packages that we’ll need.  I’m using AutoMapper 7.0.1 and AutoMapper.Extensions.Microsoft.DependencyInjection 5.0.1, which provides AutoMapper extensions on IServiceCollection that allow us to wire-up AutoMapper in Startup.ConfigureServices(IServiceCollection services).

Create Models

Suppose we have a Stock domain model class that looks like this:

    public class Stock
    {
        private readonly IStockRepository _repository;

        public Stock(IStockRepository repo)
        {
            _repository = repo ?? throw new ArgumentNullException(nameof(repo));
        }

        public string Name { get; set; }

        public string Symbol { get; set; }
    }

Note that we’re injecting an IStockRepository.  (I often have the aggregate roots in my domain models take a reference to their corresponding repository so I can do neat things like stock.SaveAsync(), but that’s another topic…)

For our purposes here it doesn’t really matter what IStockRepository and StockRepository look like – we’ll just leave them empty:

    public interface IStockRepository
    {
    }

    public class StockRepository : IStockRepository
    {
    }

We have a StockViewModel class that roughly corresponds with our Stock domain model and looks like this:

 
    public class StockViewModel
    {
        public string Name { get; set; }

        public string Symbol { get; set; }
    }

We’d also probably have a Stock storage model class and perhaps other corresponding view model representations of a stock, but we’ll keep it to just these two classes to keep things simple.

Create Mapping Profile

Next we’ll create our mapping profile, which is where we’ll configure our specific type mappings.  Usually I’ll create a profile per bounded context per layer I’m mapping.  So, for example, if I have a single bounded context but I map between storage model ↔ domain model and between domain model ↔ view model, I’ll have a ViewModelMappingProfile and a StorageModelMappingProfile.

Our ViewModelMappingProfile in this case looks like this:

 
    public class ViewModelMappingProfile : Profile
    {
        public ViewModelMappingProfile()
        {           
            CreateMap<StockViewModel, Stock>().ConstructUsingServiceLocator();
        }
    }

It derives from AutoMapper.Profile.  I’ve just got a single, simple mapping from StockViewModel to Stock, and I’ve called ConstructUsingServiceLocator() on the mapping to enable the resolution and injection of dependencies into the target.  Comprehensive documentation on configuring individual mappings can be found here.

Configure Services

Next we configure our services and add AutoMapper to the service collection.   In Startup.ConfigureServices(IServiceCollection services)we add the following after services.AddMvc():

    services.AddAutoMapper();

The line above does a couple of things: it registers IMapper with our DI container, and it searches our assembly for classes that inherit from AutoMapper.Profileand automatically loads them.  So our ViewModelMappingProfile gets loaded automatically at startup, and we can confirm that by putting a breakpoint in its constructor.

We also must register with the DI container any dependencies that will be injected in at map time (like our IStockRepository) as well as any type mappings that are configured with ConstructUsingServiceLocator()(like our Stock domain class).  This part is important when working with the ASP.NET Core DI container – other third-party containers that I’ve worked with in the past (such as Unity) will resolve concrete types automatically without requiring an explicit type mapping.  Not so with the ASP.NET Core DI container, and I spent a bit of time figuring this out.  Since we define the mapping to Stock with ConstructUsingServiceLocator(), it must be explicitly registered with the container.

So, we have the following somewhere in our Startup.ConfigureServices(IServiceCollection services) method:

    services.AddScoped<IStockRepository, StockRepository>();
    services.AddTransient<Stock, Stock>();

Map!

With our configuration complete, all that’s left to do is start using our mappings.  We can inject an instance of IMapper into any of our classes that are resolved using the DI container.

Here’s an example usage in an OnPostAsync() method on a Razor page model that maps from a posted view model to the domain model and then saves the data:

        public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var stock = _mapper.Map<StockViewModel, Stock>(Stock);
            await stock.SaveAsync(cancellationToken);

            return RedirectToPage($"./Stock?id={stock.Id}");
        }

Wrapping Up

We’ve shown how to configure and start using AutoMapper with the ASP.NET Core DI container, for a quick and easy way to get model mapping with DI working in your ASP.NET Core project.

It’s worth noting, though, that while the core DI container may be adequate for small, simple projects, it’s not as feature-rich as most other third-party DI containers.  More complex projects might require a more robust and capable DI container to meet their specific needs.  (This post on Stack Overflow gives a nice overview of some of its shortcomings.)

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax