[Dev Tip] Automatic registration of AutoMapper profiles with the Unity dependency injection container


Normally when you create AutoMapper profile, it is you responsibility to register them. However with the dependency injection container (DI) you have great possibility to automate this.

Sample Application

I will show you how to register AutoMapper profiles with the help of unit tests. For the simplicity: the class CustomerRepository implements just only one methodGetAllCustomers, which is just only tested method. This method use the AutoMapper to map collection of objects of type CustomerEntity to collection of objects of typeCustomerDomain.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CustomerRepository : ICustomerRepository
{
    private readonly IMappingEngine _mappingEngine;
    private readonly IStorage _storage;
    public CustomerRepository(IMappingEngine mappingEngine, IStorage storage)
    {
        if (mappingEngine == null)
        {
            throw new ArgumentNullException("mappingEngine");
        }
        if (storage == null)
        {
            throw new ArgumentNullException("storage");
        }
        _mappingEngine = mappingEngine;
        _storage = storage;
    }
    public IEnumerable<CustomerDomain> GetAllCustomers()
    {
        IEnumerable<CustomerEntity> customerEntities = _storage.GetAllCustomers();
        return _mappingEngine.Map<IEnumerable<CustomerDomain>>(customerEntities);
    }
}
1
2
3
4
5
6
7
public class CustomerEntity
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string Surname { get; set; }
   public string Comment { get; set; }
}
1
2
3
4
5
6
public class CustomerDomain
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string Surname { get; set; }
}

The CustomerEntity and CustomerDomain classes are almost the identical, except theCustomerDomain don’t have the property Comment, which is also excluded from the mapping in the AutoMapper profile.

1
2
3
4
5
6
7
8
public class CustomerDomainToCustomerEntityProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<CustomerDomain, CustomerEntity>()
            .ForMember(customerEntity => customerEntity.Comment, opt => opt.Ignore());
    }
}

Registration without DI

Typically you would do something like this to register AutoMapper profile.

1
Mapper.Initialize(mapperConfiguration => mapperConfiguration.AddProfile(new CustomerDomainToCustomerEntityProfile()));

Manually registration is just fine, but for each new profile you must adapt the configuration.

Because with this approach we are initializing AutoMapper once for each application domain, we need use another initialization mechanism to simulate missing and correct configuration in the unit tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[TestMethod]
public void GetAllCustomers_ManualMappingConfiguration_AllCustomersAreReturned()
{
    //Arrange
    var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
    configurationStore.AddProfile(new CustomerEntityToCustomerDomainProfile());
    IMappingEngine mappingEngine = new MappingEngine(configurationStore);
    IStorage storage = new MemoryStorage();
    ICustomerRepository customerRepository = new CustomerRepository(mappingEngine, storage);
    Mapper.Initialize(mapperConfiguration => mapperConfiguration.AddProfile(new CustomerDomainToCustomerEntityProfile()));
    //Act
    IEnumerable<CustomerDomain> actualCustomers = customerRepository.GetAllCustomers();
    //Assert
    Mapper.AssertConfigurationIsValid();
    CollectionAssert.AreEqual(GetExpectedCustomers().ToList(), actualCustomers.ToList());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private IEnumerable<CustomerDomain> GetExpectedCustomers()
{
    yield return new CustomerDomain
                 {
                     Id = 1,
                     FirstName = "Anton",
                     Surname = "Kalcik"
                 };
    yield return new CustomerDomain
                 {
                     Id = 2,
                     FirstName = "Max",
                     Surname = "Mustermann"
                 };
    yield return new CustomerDomain
                 {
                     Id = 3,
                     FirstName = "Peter",
                     Surname = "Sample"
                 };
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MemoryStorage : IStorage
{
    public IEnumerable<CustomerEntity> GetAllCustomers()
    {
        yield return new CustomerEntity
                     {
                         Id = 1,
                         FirstName = "Anton",
                         Surname = "Kalcik",
                         Comment = "Some Comment"
                     };
        yield return new CustomerEntity
                     {
                         Id = 2,
                         FirstName = "Max",
                         Surname = "Mustermann",
                         Comment = "Some Comment"
                     };
        yield return new CustomerEntity
                     {
                         Id = 3,
                         FirstName = "Peter",
                         Surname = "Sample",
                         Comment = "Some Comment"
                     };
    }
}

As we are creating the new instance of MappingEngine in the each unit test instead of using Mapper.Engine, each unit test is independent from each other. We can simulate the missing configuration, for example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[TestMethod]
[ExpectedException(typeof(AutoMapperMappingException))]
public void GetAllCustomers_MissingMappingConfiguration_AutoMapperMappingException()
{
    //Arrange
    var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
    IMappingEngine mappingEngine = new MappingEngine(configurationStore);
    IStorage storage = new MemoryStorage();
    ICustomerRepository customerRepository = new CustomerRepository(mappingEngine, storage);
    //Act
    IEnumerable<CustomerDomain> actualCustomers = customerRepository.GetAllCustomers();
    //Assert
    Mapper.AssertConfigurationIsValid();
    CollectionAssert.AreEqual(GetExpectedCustomers().ToList(), actualCustomers.ToList());
}

Registration with the DI

The nice thing about the DI is that you can use assembly scanning to find all AutoMapperprofiles, wherever they are.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static class UnityExtensions
{
    public static void RegisterAutoMapperType(this IUnityContainer container, LifetimeManager lifetimeManager = null)
    {
        RegisterAutoMapperProfiles(container);
        var profiles = container.ResolveAll<Profile>();
        var autoMapperConfigurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
        profiles.Each(autoMapperConfigurationStore.AddProfile);
        autoMapperConfigurationStore.AssertConfigurationIsValid();
        container.RegisterInstance<IConfigurationProvider>(autoMapperConfigurationStore, new ContainerControlledLifetimeManager());
        container.RegisterInstance<IConfiguration>(autoMapperConfigurationStore, new ContainerControlledLifetimeManager());
        container.RegisterType<IMappingEngine, MappingEngine>(lifetimeManager ?? new TransientLifetimeManager(), new InjectionConstructor(typeof(IConfigurationProvider)));
    }
    private static void RegisterAutoMapperProfiles(IUnityContainer container)
    {
        IEnumerable<Type> autoMapperProfileTypes = AllClasses.FromAssemblies(AppDomain.CurrentDomain.GetAssemblies())
                       .Where(type => type != typeof(Profile) && typeof(Profile).IsAssignableFrom(type));
        autoMapperProfileTypes.Each(autoMapperProfileType =>
            container.RegisterType(typeof(Profile),
            autoMapperProfileType,
            autoMapperProfileType.FullName,
            new ContainerControlledLifetimeManager(),
            new InjectionMember[0]));
    }
}

With this sample extension method, you can register all AutoMapper profiles. In theRegisterAutoMapperProfiles is called method FromAssemblies of class AllClasses provided byUnity. On this way are we getting all types which inherits from the AutoMapper.Profiles and register them in the Unity container.

In the extension method RegisterAutoMapperType is registered each AutoMapper profile within instance of ConfigurationStore. Instance of ConfigurationStore is later injected in the constructor of class MappingEngine.

I leave it on the user which LifeTimeManager he want use within class MappingEngine, but I recommend ContainerControlledLifetimeManager, which correspond to the Singletoninstance.

The unit test below show you the usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[TestMethod]
public void GetAllCustomers_UseDependencyInjectionContainer_AllCustomersAreReturned()
{
    //Arrange
    IUnityContainer unityContainer = DependencyInjectionContainerConfiguration.InitializeContainer(new ContainerControlledLifetimeManager());
    var customerRepository = unityContainer.Resolve<ICustomerRepository>();
    //Act
    IEnumerable<CustomerDomain> actualCustomers = customerRepository.GetAllCustomers();
    //Assert
    Mapper.AssertConfigurationIsValid();
    CollectionAssert.AreEqual(GetExpectedCustomers().ToList(), actualCustomers.ToList());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public static class DependencyInjectionContainerConfiguration
{
    public static IUnityContainer InitializeContainer(LifetimeManager autoMapperLifetimeManager = null)
    {
        IUnityContainer unityContainer = new UnityContainer();
        unityContainer.RegisterAutoMapperType(autoMapperLifetimeManager);
        unityContainer.RegisterType<IStorage, MemoryStorage>();
        unityContainer.RegisterType<ICustomerRepository, CustomerRepository>();
        return unityContainer;
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s