13
votes

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?

4

4 Answers

22
votes

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();
    }
}
4
votes

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
votes

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();
0
votes

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.