13

I've been using Automapper and Autofac in a .Net app for some time. I configured them this way:

builder.RegisterAssemblyTypes(typeof (OneOfMyMappingProfiles).Assembly)
        .Where(t => t.IsSubclassOf(typeof (Profile)))
        .As<Profile>();

builder.Register(ctx => new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers))
        .AsImplementedInterfaces()
        .SingleInstance()
        .OnActivating(x =>
        {
            foreach (var profile in x.Context.Resolve<IEnumerable<Profile>>())
            {
                x.Instance.AddProfile(profile);
            }
        });

builder.RegisterType<MappingEngine>()
            .As<IMappingEngine>().SingleInstance();

With the latest build of Automapper (4.2) the API has changed and I am having trouble translating to the new API. ConfigurationStore no longer seems to exist. According to the docs, the way to register with an IOC is now like this:

 var profiles =
        from t in typeof (AutoMapperRegistry).Assembly.GetTypes()
        where typeof (Profile).IsAssignableFrom(t)
        select (Profile)Activator.CreateInstance(t);

    var config = new MapperConfiguration(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

    For<MapperConfiguration>().Use(config);
    For<IMapper>().Use(ctx => ctx.GetInstance<MapperConfiguration>().CreateMapper(ctx.GetInstance));

BUT that is using StructureMap. The first half of this is no problem, but I am not sure how to translate the "For<>.Use()" portion. How do I do that in Autofac?

Ethan Schofer
  • 1,718
  • 5
  • 26
  • 55

4 Answers4

22

OK. Worked it out. Here is the replacement:

var profiles =
        from t in typeof(LocationMappingProfile).Assembly.GetTypes()
        where typeof(Profile).IsAssignableFrom(t)
        select (Profile)Activator.CreateInstance(t);

        builder.Register(ctx => new MapperConfiguration(cfg =>
        {
            foreach (var profile in profiles)
            {
                cfg.AddProfile(profile);
            }
        }));

        builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>();

UPDATE

Here is an example of a profile. Super simple. In this case I only have one mapping. But I could add others. I try to keep them logically together by Entity. So in this case, any future mapping from or to ProviderDetail would be in this file. Mappings to a different entity would be in a separate mappingprofile class. Nothing injected in the profile class:

 public class ProviderMappingProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<ProviderDetail, ProviderListItem>();
    }
}

UPDATE2

Here is an example of a test that proves the mapping is correct:

public class ProviderMappingProfileTests
{
    [Fact]
    public void CreateMap_ProviderDetailToProviderQueryResult_IsValid()
    {
        var config = new MapperConfiguration(cfg =>
            cfg.CreateMap<ProviderDetail, ProviderListItem>()
            );

        config.AssertConfigurationIsValid();
    }
}
Ethan Schofer
  • 1,718
  • 5
  • 26
  • 55
  • Can you show one of your profiles? Do you inject an IMapperConfiguration into a profile constructor? – BBauer42 Apr 06 '16 at 12:25
  • Thanks! I found [this thread on configuring the new version of AutoMapper with Autofac](https://github.com/AutoMapper/AutoMapper/issues/1109) on GitHub which was also very helpful. – BBauer42 Apr 06 '16 at 13:29
  • One quick follow up question. Where and how do assert the configuration is valid? – BBauer42 Apr 06 '16 at 13:35
  • As the text of the answer says, nothing can be injected into the profiles. This is because the profiles are created using Activator.CreateInstance. In order to inject any dependencies, we'd need to register the profiles in the container then resolve the profiles from the container in the Autofac `Register` lambda. – Josh Gallagher Apr 09 '16 at 10:36
  • This code creates a new mapping configuration and mapper per lifetime scope. – Martijn B Sep 15 '16 at 09:00
4

Another interesting method of registering Automapper through Autofac, with a dynamic registration of all custom profiles in your assemblies.

Here you register profiles for all assemblies you need.

builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Program)))
    .Where(t => t.BaseType == typeof(Profile)
                && !t.IsAbstract && t.IsPublic)
    .As<Profile>();

And here you resolve those:

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

builder.Register(ctx => ctx.Resolve<MapperConfiguration>()
                           .CreateMapper())
       .As<IMapper>()
       .SingleInstance();

Also, can use

.CreateMapper(ctx.Resolve)

instead of

.CreateMapper()

to resolve internal dependencies in profiles. But it will require removing

.SingleInstance()

from registration.

And here is a usage BTW:

public SomeAutowiredClass
{
        public IMapper Mapper { get; set; }

        public void SomeMethod(SomeModel model){
             Mapper.Map<AnotherModel>(model)
        }
}
3

I agreed above solution. Instead of using reflection can't we do like below?.

public static IMapper RegisterAutoMapper()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<ApiMappingProfile1>();
        cfg.AddProfile<ApiMappingProfile2>();
        // Add all your profiles
    });
    var mapper = config.CreateMapper();
    return mapper;
}

In Autofac registration

builder.Register(c => AutoMapperConfig.RegisterAutoMapper()).As<IMapper>()
    .InstancePerLifetimeScope().PropertiesAutowired().PreserveExistingDefaults();
Prasad Kanaparthi
  • 6,423
  • 4
  • 35
  • 62
  • 1
    This will be harder to maintain over time as I add more Profiles. With a small number of profiles this may perform better, but on a larger project its just a place for me to forget to add something – Ethan Schofer Feb 19 '16 at 18:14
0

Here is a little modification, that works for me. It scans all loaded assemblies and loads all AutoMapper profiles from them.

    var autoMapperProfileTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(p => typeof(Profile).IsAssignableFrom(p) && p.IsPublic && !p.IsAbstract));
    var autoMapperProfiles = autoMapperProfileTypes.Select(p => (Profile)Activator.CreateInstance(p));
    builder.Register(ctx => new MapperConfiguration(cfg =>
    {
        foreach (var profile in autoMapperProfiles)
        {
            cfg.AddProfile(profile);
        }
    }));
    builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>().PropertiesAutowired();

It needs to filter abstract and private classes to skip AutoMapper builtin ones.

Pavel
  • 362
  • 4
  • 13