In The craft
Adding Custom Converters in AutoMapper with Assembly Scanning
Automapper is a wonderful tool. Those who haven’t used are missing out.
Simply put, AutoMapper is a convention based object to object mapper. For example, your application has a boatload of view models. How are the view models mapped to domain models? Before AutoMapper, it was lines of left to right value setting code. Using Automapper eliminates much of this code. To learn more visit http://automapper.org (I’m not affliated with AutoMapper. I’m just a fan).
In my current project I use AutoMapper to map domain models to view models and vice versa. For each domain model this equates to roughly two ITypeConverter implementations. Predictably, the number of mappings have increased as the application has grown. So much so Visual Studio began having trouble parsing the list of mappings.
Here is a short sample of custom converters:
c.CreateMap<NewAgency, Agency>().ConvertUsing(new NewAgencyToAgencyConverter());
c.CreateMap<DependentModel, EmployerGroupMember>().ConvertUsing(new DependentModelToMemberConverter());
c.CreateMap<EmployeeModel, EmployerGroupMember>().ConvertUsing(new EmployeeToMemberConverter());
c.CreateMap<NewEmployerGroup, EmployerGroup>().ConvertUsing(new NewEmployerGroupToEmployerGroupConverter());
c.CreateMap<UpdateEmployerGroup, EmployerGroup>().ConvertUsing(new UpdateEmployerGroupToEmployerGroupConverter());
c.CreateMap<EmployerGroupMember, object>().ConvertUsing(new EmployerGroupMemberToResultConverter());
c.CreateMap<EmployerGroup, object>().ConvertUsing(new EmployerGroupToResultConverter());
c.CreateMap<EmployerGroupAddress, object>().ConvertUsing(new EmployerGroupAddressToObjectConverter());
c.CreateMap<NewLocation, EmployerGroupAddress>().ConvertUsing(new NewLocationToEmployerGroupAddressConverter());
c.CreateMap<UpdateLocation, EmployerGroupAddress>().ConvertUsing(new UpdateLocationToEmployerGroupAddressConverter());
c.CreateMap<User, object>().ConvertUsing(new UserToObjectResult());
c.CreateMap<List<Carrier>, object>().ConvertUsing(new CarrierCollectionToResultConverter());
c.CreateMap<Benefit, object>().ConvertUsing(new BenefitToResultConverter());
c.CreateMap<List<Benefit>, object>().ConvertUsing(new BenefitCollectionToResultConverter());
c.CreateMap<NewBenefit, Benefit>().ConvertUsing(new NewBenefitToBenefitConverter());
I can’t tell you how many times I created a Customer Converter and forgot to add it to the list. With assembly scanning, all this pain goes away. The downside is AutoMapper does not support assembly scanning. However most modern Dependency Injection containers do. So all we need to do is use a DI container that has scanning capabilities. My preferred container is StructureMap which does support assembly scanning.
Firstly, AutoMapper’s ITypeConverter interface needs to be added to StructureMaps manifest
x.Scan(scan => { scan.ConnectImplementationsToTypesClosing(typeof(ITypeConverter<,>));
});
Retrieving the ITypeConverter’s implementation isn’t as easy as it seems. My first attempt was to use StrucutureMaps’s GetAllInstances method:
var items = ObjectFactory.GetAllInstances(typeof(ITypeConverter<,>));
No Cigar. At first I was mystified. Why this didn’t work? After all this is how I registered the implementations. Without going into detail, StructureMap doesn’t track this type of information. It finds all the implementations of ITypeConverter<,> and add the concrete types to the manifest. Most developers are don’t want to retrieve all the implementations by the open generic interface this information is discarded.
It turns out that to get the implementations of ITypeConverter<,>, is a bit harder than I thought. A little reflection magic is needed:
private static IEnumerable<object> GetITypeConverters()
{
IEnumerable<IPluginTypeConfiguration> handlers =
ObjectFactory.Container.Model.PluginTypes
.Where(x => x.PluginType.IsGenericType &&
x.PluginType.GetGenericTypeDefinition() ==
typeof (ITypeConverter<,>))
.ToList();
var allInstances = new List<object>();
foreach (IPluginTypeConfiguration pluginTypeConfiguration in handlers)
{
var instancesForPluginType = ObjectFactory.GetAllInstances(pluginTypeConfiguration.PluginType).OfType<object>();
allInstances.AddRange(instancesForPluginType);
}
return allInstances;
}
We now have all the implementations of ITypeConverter<,>. The next step is to add them to AutoMapper.
public static void ConfigureAutoMapper()
{
var items = GetITypeConverters();
Mapper.Initialize(c =>
{
foreach (var item in items)
{
string interfaceName = typeof (ITypeConverter<,>).FullName;
c.CreateMap(item.GetType().GetInterface(interfaceName).GenericTypeArguments[0], item.GetType().GetInterface(interfaceName).GenericTypeArguments[1]).ConvertUsing(item.GetType());
}
});
}
That’s it. It’s not as straight forward as it could be, but it works. Luckily this code runs once at application startup otherwise we might have performance concerns.
Here is the complete code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using AutoMapper;
using StructureMap;
using StructureMap.Query;
namespace Grover.Api.App_Start
{
public class AutoMapperInitialize
{
public static void ConfigureAutoMapper()
{
var items = GetITypeConverters();
Mapper.Initialize(c =>
{
foreach (var item in items)
{
string interfaceName = typeof (ITypeConverter<,>).FullName;
c.CreateMap(item.GetType().GetInterface(interfaceName).GenericTypeArguments[0], item.GetType().GetInterface(interfaceName).GenericTypeArguments[1]).ConvertUsing(item.GetType());
}
});
}
private static IEnumerable<object> GetITypeConverters()
{
IEnumerable<IPluginTypeConfiguration> handlers =
ObjectFactory.Container.Model.PluginTypes
.Where(x => x.PluginType.IsGenericType &&
x.PluginType.GetGenericTypeDefinition() ==
typeof (ITypeConverter<,>))
.ToList();
var allInstances = new List<object>();
foreach (IPluginTypeConfiguration pluginTypeConfiguration in handlers)
{
var instancesForPluginType = ObjectFactory.GetAllInstances(pluginTypeConfiguration.PluginType).OfType<object>();
allInstances.AddRange(instancesForPluginType);
}
return allInstances;
}
}
}