Using dependency injection in your AutoMapper profile

So, one of my previous customers reached out to me a couple of weeks ago. They had a question concerning on how to use dependency injection in their AutoMapper profiles. For this project we were using profiles which were dynamically loaded inside the application using MEF and were using Autofac for dependency injection.

The way you would normally load all of these profiles is by using the AddProfiles method when initializing AutoMapper. The code would look similar to the following excerpt.

private static void RegisterAutomapperDefault(IEnumerable<Assembly> assemblies)
{
    AutoMapper.Mapper.Initialize(cfg =>
    {
        cfg.AddProfiles(assemblies);
    });
}

This works fine on most occasions and is the recommended approach, to my knowledge.

When you start thinking about using dependency injection (constructor injection in this case), you might want to rethink your mapping profile. Because, if you have the need for dependencies when mapping object properties to the properties of a different object it probably means there’s too much logic going on over here.

Of course, if you need this, one thing you might want to consider is using the custom type convertors or custom value resolvers. You can use dependency injection (constructor injection) using these convertors and resolvers by adding a single line in the Initialize method of AutoMapper.

private static void RegisterAutomapperDefault(IContainer container, IEnumerable<Assembly> assemblies)
{
    AutoMapper.Mapper.Initialize(cfg =>
    {
        cfg.ConstructServicesUsing(container.Resolve);

       cfg.AddProfiles(assemblies);
    });
}

Now if you still feel like you need to do constructor injection inside your mapping Profile classes, that’s also quite possible, but please think about it in before doing so.

In order to get this working, I first created a new Profile class which injects an IConvertor, like below.

public class MyProfile : Profile
{
    public MyProfile(IConvertor convertor)
    {
        CreateMap<Model, ViewModel>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Identifier))
            .ForMember(dest => dest.Name, opt => opt.MapFrom(src => convertor.Execute(src.SomeText)))
            ;
    }
}

What you need to do now is register all of the Profile implementations to your IoC-framework, like Autofac. To do this, you have to do some reflection magic. The code used below retrieves all Profile implementations in the assemblies which have their name starting with “Some”.

public static IContainer Autofac()
{
    var containerBuilder = new ContainerBuilder();
    // Register the dependencies...
    containerBuilder.RegisterType<Convertor>().As<IConvertor>();
    var loadedProfiles = RetrieveProfiles();
    containerBuilder.RegisterTypes(loadedProfiles.ToArray());
    var container = containerBuilder.Build();
    RegisterAutoMapper(container, loadedProfiles);
    return container;
}

/// <summary>
/// Scan all referenced assemblies to retrieve all `Profile` types.
/// </summary>
/// <returns>A collection of <see cref="AutoMapper.Profile"/> types.</returns>
private static List<Type> RetrieveProfiles()
{
    var assemblyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies()
        .Where(a => a.Name.StartsWith("Some"));
    var assemblies = assemblyNames.Select(an => Assembly.Load(an));
    var loadedProfiles = ExtractProfiles(assemblies);
    return loadedProfiles;
}


private static List<Type> ExtractProfiles(IEnumerable<Assembly> assemblies)
{
    var profiles = new List<Type>();
    foreach (var assembly in assemblies)
    {
        var assemblyProfiles = assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Profile)));
        profiles.AddRange(assemblyProfiles);
    }
    return profiles;
}

All of this code is just to register your mapping profiles to Autofac. This way Autofac can resolve them when initializing AutoMapper. To register your mapping profiles in AutoMapper you need to use a specific overload of the AddProfile method which takes a Profile instance, instead of a type.

/// <summary>
/// Over here we iterate over all <see cref="Profile"/> types and resolve them via the <see cref="IContainer"/>.
/// This way the `AddProfile` method will receive an instance of the found <see cref="Profile"/> type, which means
/// all dependencies will be resolved via the <see cref="IContainer"/>.
/// </summary>
private static void RegisterAutoMapper(IContainer container, IEnumerable<Type> loadedProfiles)
{
     AutoMapper.Mapper.Initialize(cfg =>
    {
        cfg.ConstructServicesUsing(container.Resolve);
       foreach (var profile in loadedProfiles)
        {
            var resolvedProfile = container.Resolve(profile) as Profile;
            cfg.AddProfile(resolvedProfile);
        }

    });
}

You can see I’m resolving all of the loaded profiles via Autofac and add each resolved instance to AutoMapper.

This takes quite a bit of effort, but resolving your profiles like this will give you the possibility to do any kind of dependency injection inside your AutoMapper code.

Just remember, as I’ve written before: “Just because you can, doesn’t mean you should!”

Still I wanted to show you how this can be done as it’s kind of cool. If you want to check out the complete solution, check out my GitHub repository for this project.


Share

comments powered by Disqus