Modular AutoMapper registrations with Autofac

Posted: 21 February 2017 in Development

If you find yourself mapping objects from one to another in your projects and haven't looked at AutoMapper by Jimmy Bogard you should give it a try. AutoMapper helps simplify object to object mapping by handling the tedious parts for you with a simple convention based mapping system. In this post we'll take a look at one method to configure and use AutoMapper with MVC and AutoFac as our Inversion of Control container.

Object to object mapping

If you are handling data in your MVC application then the chances are that you are performing some sort of object to object mapping, for instance using ViewModels to present data to your application users. Without something like AutoMapper handling object mapping you might have something like this in your MVC controller to perform an update on a Product:

public class ProductsController : Controller {
    private readonly IProductRepository _productRepository;

    public ProductsController(IProductRepository productRepository) {
        _productRepository = productRepository;
    }
    ...
    [HttpPost]
    public ActionResult Update(ProductModel form) {
        var product = new Product {
            Id = form.Id,
            Name = form.Name,
            Description = form.Description,
            Price = form.Price,
            StockQuantity = form.StockQuantity
        };
        _productRepository.Update(product);
        return View(form);
    }
    ...
}

This is likely to be familiar in one form or another, you need to manually assign the properties from one object to another before updating your data store. This approach tends to be prone to errors, either through copy-paste mistakes or simply assigning property values incorrectly.

AutoMapper helps with this, allowing us to register objects that can be mapped to one another, and how they should be mapped. It handles a lot of mapping by convention, properties with the same name will be mapped automagically and for any custom mappings that are required that can be done at the point of registering the mappings.

Let's get a look at the simple objects to be mapped in our example.

Our objects to be mapped

The Product class:

This is our basic model that transfers data to and from our database.

public class Product {
    public int Id {get; set;}
    public string Name {get; set;}
    public string Description {get; set;}
    public double Price {get; set;}
    public int StockQuantity {get; set;}
}

The ProductModel class:

This class acts as a ViewModel, transferring data to and from a view in an MVC project. This is a simple example, in a real project this would likely have more logic - I've added the Available property just to illustrate it's difference from the Product class.

public class ProductModel {
    public int Id {get; set;}
    public string Name {get; set;}
    public string Description {get; set;}
    public double Price {get; set;}
    public int StockQuantity {get; set;}
    
    public bool Available {
        get {
            return StockQuantity > 0;
        }
    }
}

So with our objects in place next we'll look at how to register mappings for these objects with AutoMapper.

AutoMapper Profile mappings

Registering objects to be mapped with AutoMapper can be done in multiple ways, the approach we'll use is to use a Profile. Profiles in AutoMapper give us a way to organise our mappings allowing the grouping of related mappings together, we can have multiple profiles in an application or have mappings configured across multiple projects.

using AutoMapper;

public class ProductProfile : Profile {
    protected override void Configure() {
        CreateMap<Product, ProductModel>();
        CreateMap<ProductModel, Product>();
    }
}

Here we have created mappings for the objects above, allowing us to map to and from each object. When creating your mappings you can configure custom mappings if required, please refer to the AutoMapper documentation for examples.

Autofac registration

The final part of the configuration is to set up and register the AutoMapper IMapper interface with our Inversion of Control container, in this case Autofac. I've used an Autofac Module here to configure the mapper but you can take whatever approach works best for you.

using System;
using System.Collections.Generic;
using AutoMapper;
using Autofac;

public class MapperInstaller : Module {
    protected override void Load(ContainerBuilder builder) {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        builder.RegisterAssemblyTypes(assemblies)
            .Where(t => typeof(Profile).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic)
            .As<Profile>();

        builder.Register(c => new MapperConfiguration(cfg => {
            foreach (var profile in c.Resolve<IEnumerable<Profile>>()) {
                cfg.AddProfile(profile);
            }
        })).AsSelf().SingleInstance();

        builder.Register(c => c.Resolve<MapperConfiguration>()
            .CreateMapper(c.Resolve))
            .As<IMapper>()
            .InstancePerLifetimeScope();
    }
}

In this module we have firstly scanned through all assemblies in our project and registered all our AutoMapper profiles. Then we add these profiles to an AutoMapper MapperConfiguration and finally register the IMapper interface, which will resolve to a new instance of the mapper using the registered MapperConfiguration.

Once this is done we can then change our initial ProductsController to inject the IMapper interface and use it to map our objects. The new leaner version would look like this:

public class ProductsController : Controller {
    private readonly IProductRepository _productRepository;
    private readonly IMapper _mapper;

    public ProductsController(IProductRepository productRepository, IMapper mapper) {
        _productRepository = productRepository;
        _mapper = mapper;
    }
    ...
    [HttpPost]
    public ActionResult Update(ProductModel form) {
        var product = _mapper.Map<Product>(form);
        _productRepository.Update(product);
        return View(form);
    }
    ...
}

This is a simpler controller than before, and with more complex objects involved this approach can really save a lot of coding, repetition and time.

Mapping modularity

Apart from centralisation of mapping logic, this approach is very useful if you need to distribute your mappings across multiple assemblies. As mentioned above when registering our profiles we scan through all project assemblies when we are configuring Autofac, allowing you to have profiles that will be automatically registered at runtime.

I've found this mechanism of mapping registration very useful, hopefully it might be of use to you too.

comments powered by Disqus

Get in touch...

Drop us a line at contact@protomatter.co.uk, or call on +44(0) 28 417 54771.