Autofac support for AutoMapper
I have been doing .NET development for quite a while now. Sometimes people ask me how is it possible that I still find it interesting. Well, the answer is easy: I'm super D.R.Y. If you are not familiar with the term, you should look it up, but it basically means that you should always try to apply solutions that can be, or better yet, that are already reaused. Don't repeat yourself.
One good practice is automating every possible thing with a framework that's already out there. Resolving object graphs resulting from using dependency injection is a common task that should be automated. My choice for this is Autofac when it comes to good old .NET framework development. Of course, the .NET Core based frameworks have their own :)
Another common thing that comes up very often in a web application is transforming viewmodels or DTOs to domain objects and vice versa. Again, in an attempt to automate as much of the process as possible, I usually use AutoMapper.
The problem is that AutoMapper and Autofac don't play very well together. The Autofac project is very active on Github and they do have support for a lot of different frameworks, but not AutoMapper. And while AutoMapper also supports a number of different frameworks, and even the DI framework of ASP.NET Core MVC, it doesn't have anything for regular old ASP.NET MVC or Web API 2. And that's just a shame :)
So looked at the source code of the Automapper ASP.NET Core MVC integration for inspiration. This configuration adds the AutoMapper and its services so you can easily inject IMapper
to your controllers. The registrations are:
- As a singleton for the MapperConfiguration
- As a transient instance for IMapper
- ITypeConverter instances as transient
- IValueConverter instances as transient
- IValueResolver instances as transient
- IMemberValueResolver instances as transient
- IMappingAction instances as transient
I'm guessing there were a lot of reasons for why these are the registrations for the different services, so I decided that I would implement something like this for Autofac as well. I basically created a new Autofac module, copied the code over, removed a couple things that I thought were unnecessary, changed it a bit so that it uses the Autofac methods, and there you have it:
public class AutoMapperModule : Autofac.Module
{
private readonly IEnumerable<Assembly> assembliesToScan;
public AutoMapperModule(IEnumerable<Assembly> assembliesToScan)
{
this.assembliesToScan = assembliesToScan;
}
public AutoMapperModule(params Assembly[] assembliesToScan) : this((IEnumerable<Assembly>)assembliesToScan) { }
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
var assembliesToScan = this.assembliesToScan as Assembly[] ?? this.assembliesToScan.ToArray();
var allTypes = assembliesToScan
.Where(a => !a.IsDynamic && a.GetName().Name != nameof(AutoMapper))
.Distinct() // avoid AutoMapper.DuplicateTypeMapConfigurationException
.SelectMany(a => a.DefinedTypes)
.ToArray();
var openTypes = new[] {
typeof(IValueResolver<,,>),
typeof(IMemberValueResolver<,,,>),
typeof(ITypeConverter<,>),
typeof(IValueConverter<,>),
typeof(IMappingAction<,>)
};
foreach (var type in openTypes.SelectMany(openType =>
allTypes.Where(t => t.IsClass && !t.IsAbstract && ImplementsGenericInterface(t.AsType(), openType))))
{
builder.RegisterType(type.AsType()).InstancePerDependency();
}
builder.Register<IConfigurationProvider>(ctx => new MapperConfiguration(cfg => cfg.AddMaps(assembliesToScan))).SingleInstance();
builder.Register<IMapper>(ctx => new Mapper(ctx.Resolve<IConfigurationProvider>(), ctx.Resolve)).InstancePerDependency();
}
private static bool ImplementsGenericInterface(Type type, Type interfaceType)
=> IsGenericType(type, interfaceType) || type.GetTypeInfo().ImplementedInterfaces.Any(@interface => IsGenericType(@interface, interfaceType));
private static bool IsGenericType(Type type, Type genericType)
=> type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == genericType;
}
And now, you can simply add this module to the Autofac ContainerBuilder
. For ASP.NET Web API, you can pass in typeof(Startup)
to the module constuctor if you define your mapping profiles in the Web API project (and since the DTOs are only used for communication with the client, where else would they be?) and then you can use constructor injection to get an IMapper
instance.