Integrating AutoMapper with ASP.NET Core DI

Part of the release of ASP.NET Core is a new DI framework that’s completely integrated with the ASP.NET pipeline. Previous ASP.NET frameworks either had no DI or used service location in various formats to resolve dependencies. One of the nice things about a completely integrated container (not just a means to resolve dependencies, but to register them as well), means it’s much easier to develop plugins for the framework that bridge your OSS project and the ASP.NET Core app. I already did this with MediatR and HtmlTags, but wanted to walk through how I did this with AutoMapper.

Before I got started, I wanted to understand what the pain points of integrating AutoMapper with an application are. The biggest one seems to be the Initialize call, most systems I work with use AutoMapper Profiles to define configuration (instead of one ginormous Initialize block). If you have a lot of these, you don’t want to have a bunch of AddProfile calls in your Initialize method, you want them to be discovered. So first off, solving the Profile discovery problem.

Next is deciding between the static versus instance way of using AutoMapper. It turns out that most everyone really wants to use the static way of AutoMapper, but this can pose a problem in certain scenarios. If you’re building a resolver, you’re often building one with dependencies on things like a DbContext or ISession, an ORM/data access thingy:

public class LatestMemberResolver : IValueResolver<object, object, User> {
  privat readonly AppContext _dbContext;
  public LatestMemberResolver(AppContext dbContext) {
    _dbContext = dbContext;
  }
  
  public User Resolve(object source, object destination, User destMember, ResolutionContext context) {
    return _dbContext.Users.OrderByDescending(u => u.SignUpDate).FirstOrDefault();
  }
}

With the new DI framework, the DbContext would be a scoped dependency, meaning you’d get one of those per request. But how would AutoMapper know how to resolve the value resolver correctly?

The easiest way is to also scope an IMapper to a request, as its constructor takes a function to build value resolvers, type converters, and member value resolvers:

IMapper mapper 
  = new Mapper(Mapper.Configuration, t => ServiceLocator.Resolve(t));

The caveat is you have to use an IMapper instance, not the Mapper static method. There’s a way to pass in the constructor function to a Mapper.Map call, but you have to pass it in *every single time*, and thus not so useful:

Mapper.Map<User, UserModel>(user, 
  opt => opt.ConstructServicesUsing(t => ServiceLocator.Resolve(t)));

Finally, if you’re using AutoMapper projections, you’d like to stick with the static initialization. Since the projection piece is an extension method, there’s no way to resolve dependencies other than passing them in, or service location. With static initialization, I know exactly where to go to look for AutoMapper configuration. Instance-based, you have to pass in your configuration to every single ProjectTo call.

In short, I want static initialization for configuration, but instance-based usage of mapping. Call Mapper.Initialize, but create mapper instances from the static configuration.

Initializating the container and AutoMapper

Before I worry about configuring the container (the IServiceCollection object), I need to initialize AutoMapper. I’ll assume that you’re using Profiles, and I’ll simply scan through a list of assemblies for anything that is a Profile:

private static void AddAutoMapperClasses(IServiceCollection services, IEnumerable<Assembly> assembliesToScan)
{
    assembliesToScan = assembliesToScan as Assembly[] ?? assembliesToScan.ToArray();

    var allTypes = assembliesToScan.SelectMany(a => a.ExportedTypes).ToArray();

    var profiles =
    allTypes
        .Where(t => typeof(Profile).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()))
        .Where(t => !t.GetTypeInfo().IsAbstract);

    Mapper.Initialize(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

The assembly list can come from a list of assemblies or types passed in to mark assemblies, or I can just look at what assemblies are loaded in the current DependencyContext (the thing ASP.NET Core populates with discovered assemblies):

public static void AddAutoMapper(this IServiceCollection services)
{
    services.AddAutoMapper(DependencyContext.Default);
}

public static void AddAutoMapper(this IServiceCollection services, DependencyContext dependencyContext)
{
    services.AddAutoMapper(dependencyContext.RuntimeLibraries
        .SelectMany(lib => lib.GetDefaultAssemblyNames(dependencyContext).Select(Assembly.Load)));
}

Next, I need to add all value resolvers, type converters, and member value resolvers to the container. Not every value resolver etc. might need to be initialized by the container, and if you don’t pass in a constructor function it won’t use a container, but this is just a safeguard just in case something needs to resolve these AutoMapper service classes:

var openTypes = new[]
{
    typeof(IValueResolver<,,>),
    typeof(IMemberValueResolver<,,,>),
    typeof(ITypeConverter<,>)
};
foreach (var openType in openTypes)
{
    foreach (var type in allTypes
        .Where(t => t.GetTypeInfo().IsClass)
        .Where(t => !t.GetTypeInfo().IsAbstract)
        .Where(t => t.ImplementsGenericInterface(openType)))
    {
        services.AddTransient(type);
    }
}

I loop through every class and see if it implements the open generic interfaces I’m interested in, and if so, registers them as transient in the container. The “ImplementsGenericInterface” doesn’t exist in the BCL, but it probably should :) .

Finally, I register the mapper configuration and mapper instances in the container:

services.AddSingleton(Mapper.Configuration);
services.AddScoped<IMapper>(sp => 
  new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService));

While the configuration is static, every IMapper instance is scoped to a request, passing in the constructor function from the service provider. This means that AutoMapper will get the correct scoped instances to build its value resolvers, type converters etc.

With that in place, it’s now trivial to add AutoMapper to an ASP.NET Core application. After I create my Profiles that contain my AutoMapper configuration, I instruct the container to add AutoMapper (now released as a NuGet package from the AutoMapper.Extensions.Microsoft.DependencyInjection package):

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.AddAutoMapper();

And as long as I make sure and add this after the MVC services are registered, it correctly loads up all the found assemblies and initializes AutoMapper. If not, I can always instruct the initialization to look in specific types/assemblies for Profiles. I can then use AutoMapper statically or instance-based in a controller:

public class UserController {
  private readonly IMapper _mapper;
  private readonly AppContext _dbContext;
  public UserController(IMapper mapper, AppContext dbContext) {
    _mapper = mapper;
    _dbContext = dbContext;
  }
  
  public IActionResult Index() {
    var users = dbContext.Users
      .ProjectTo<UserIndexModel>()
      .ToList();
      
    return View(users);
  }
  
  public IActionResult Show(int id) {
    var user = dbContext.Users.Where(u => u.Id == id).Single();
    var model = _mapper.Map<User, UserIndexModel>(user);
    
    return View(model);
  }
}

The projections use the static configuration, while the instance-based uses any potential injected services. Just about as simple as it can get!

Other containers

While the new AutoMapper extensions package is specific to ASP.NET Core DI, it’s also how I would initialize and register AutoMapper with any container. Previously, I would lean on DI containers for assembly scanning purposes, finding all Profile classes, but this had the unfortunate side effect that Profiles could themselves have dependencies – a very bad idea! With the pattern above, it should be easy to extend to any other DI container.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASPNetCore, AutoMapper, DependencyInjection. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Hi there. We’re using this approach, in the meantime that it gets to stable release. It looks like we found an issue, not sure is intended.

    With this technique, injected `IMapper` instances will receive the “right” constructor function (`sp.GetService`). But the same doesn’t seem to happen for static `IMapper` instance obtained from `Mapper.Instance`, in places where the mapper cannot be injected as a dependency.

    In static context, as `Mapper.Initialize` does not set the constructor, corresponding `Mapper.Instance` will not know about it and use plain type activation to instantiate e.g. resolvers.

    Is that the way it is? Thanks!

    • jbogard

      Yep! Using Mapper.Map, it doesn’t have the correct contextual factory, so you’d have to use the overload to pass in the service constructor to Mapper.Map. Mapper.Initialize won’t set the constructor because as it is static could lead to all sorts of weird memory leaks and other issues.

  • Henk Mollema

    Doesn’t this line: https://gist.github.com/jbogard/ac5d48395d6d547fed392244282719be#file-dependencycontext-cs-L8
    causes a *lot* of assemblies to be scanned? In a pretty large app, it sums up to 200+ assemblies with combined 5000+ public types to be scanned. ASP.NET filters those assemblies by looking which dependencies your app actually references: https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs.

    • jbogard

      PR?

      • Problem with that approach is that it relies on the name of the entry point assembly being passed through the `IHostingEnvironment`. I don’t think `services.AddAutoMapper(typeof(Startup).Assembly))` is very friendly either.

  • Jeremy Brown

    I am using the ApplicationPartsManager to locate all of the Profiles and and register them. https://gist.github.com/Tsabo/995288b628781e9189d90b4f95dd95ac

    • jbogard

      How is that called?

      • Jeremy Brown

        In ConfigureServices(IServiceCollection services)

        • jbogard

          How do you get those assembly parts out? That’s another option for me – grab those assembly parts that I know that MVC has already added.

          I’ve pinged the MVC team a couple of times too about a good approach, unfortunately no response :(

          • Jeremy Brown

            Oh, I apologize I did leave a step out, I just forgot :( .

            Actually had a comment that said it was a temporary solution until the next release of aspnetcore.mvc. I am just now in the process of upgrade from RC2 to version 1 and I will dive into the changes to see if this was indeed solved with the version 1 release.

            For some reason I saved a link to a commit, but I don’t remember why but I’ll dig into and figure out why I saved it:

            https://github.com/aspnet/Mvc/pull/4391/commits/f638c051fa79c293ea7a0a859ed9700db9a18d71

            Attached is a screen shot showing how I am adding the parts. Sorry for the error!

  • Johnathon Sullinger

    Isn’t the following code massively inefficient?


    foreach (var openType in openTypes)
    {
    foreach (var type in allTypes
    .Where(t => t.GetTypeInfo().IsClass)
    .Where(t => !t.GetTypeInfo().IsAbstract)
    .Where(t => t.ImplementsGenericInterface(openType)))
    {
    services.AddTransient(type);
    }
    }

    You allocate an iterator in each .Where call, and iterate over the collection 3 different times. Why not allocate a single iterator and iterate just once?


    foreach (var openType in openTypes)
    {
    foreach (var type in allTypes
    .Where(t => t.GetTypeInfo().IsClass
    && !t.GetTypeInfo().IsAbstract
    && t.ImplementsGenericInterface(openType)))
    {
    services.AddTransient(type);
    }
    }

    • jbogard

      Pull request? :)

      • Johnathon Sullinger

        I’m just providing feedback to your project. Replying to everyone’s feedback with “PR?” is very dismissive of their feedback. You’re essentially telling me you have no interest in my feedback unless I submit code changes. Not something everyone using OSS projects has the time or skill set to really do.

        • jbogard

          Fair enough! For comments on code, my general approach is direct people towards creating a GitHub issue if it’s a bug/feature request/something complicated. If it’s a trivial code change, I direct towards a PR.

          And I do appreciate you’ve taken the time to respond with an honest improvement to the code (there’s an even bigger perf problem here – it scans every loaded assembly – whoops!), but for me, blog comments just come through in email. Email is one of the worst task managers, so I try as much as possible to direct blog comments that aren’t strictly questions/comments to the channel that I’m more able to respond/track/triage etc.

          So yes, today asking for a PR on a trivial code change might seem dismissive, but I’m also very hopeful that the .NET community joins nearly every other development community where this request is not seen as dismissive, but an open and welcome invitation to participate in the (soon to be ubiquitous) OSS process.

          • Johnathon Sullinger

            I get that totally. My point was in the deliver of the request, not the request itself :) The community doesn’t know what that PR should look like in a lot of cases, nor what will be acceptable by the maintainers. There needs to be a tiny bit of dialog between those in the community that will work on the issue for the maintainers (ultimately issuing a PR) and those that maintain the project, so that the community members don’t waste a bunch of time on an initial PR, only to re-write half of it because the maintainers want to go a different route. Some dialog helps prevent wasting peoples time on both ends of the fence. As you said, blog post comments aren’t the place for that conversation to take place though!

  • Aries

    I didn’t see any sample AspNetCore sample implementations of AutoMapper @ https://github.com/AutoMapper/AutoMapper/tree/master/src/AutoMapperSamples.

    Am I looking in the wrong place? This article is great, but a sample solution would take less time to read.

  • RandyDaddis

    Does anyone have the time to provide guidance on the optimal way to configure AutoMapper for an ASP.NET Core application?

    I have posted a question here: http://stackoverflow.com/questions/40114996/automapper-implementation-in-asp-net-core-mvc.

    The issue I’m having seems to relate to my profile implementation calling Mapper.Configuration.FindTypeMapFor. Is this because Mapper.Configuration is a static method? Is it possible to test for an existing mapping configuration before adding one?

    I have published a sample application here: https://github.com/RandyDaddis/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/AspNetCoreMvcSampleCode/src/Dna.NetCore.Core.DAL.AutoMapper/AutoMapperProfile_NetCore_DtoFromDao.cs

  • alltej

    I created mapper profile for Product and ProductModel. The API will return a list of Product. So I need to map that list to list of ProductModel. I was able to do this in previous version but seems breaking in 5.2 version. Any ideas?

    • jbogard

      Github issue if you don’t mind!

      • alltej

        The issue is actually in my end. So it’s all good.

  • Bill Noel

    Awesome. Thanks.