How to register AutoMapper 4.2.0 with Simple Injec

2020-02-12 04:11发布

问题:

Updated to AutoMapper 4.2.0, and following the migration guide available here: https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API/f4784dac61b91a0df130e252c91a0efd76ff51de#preserving-static-feel. Trying to translate code on that page for StructureMap to Simple Injector. Can someone show me what this code looks like in Simple Injector?

StructureMap

public class AutoMapperRegistry : Registry
{
    public AutoMapperRegistry()
    {
        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));
    }
}

Simple Injector

?

回答1:

This would be the equivalent:

container.RegisterSingleton<MapperConfiguration>(config);
container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));


回答2:

Simple Injector's IPackage interface seems like the nearest equivalent of StructureMap's Registry type. Here's the package I use, building from @Steven's answer:

using System;
using System.Linq;
using System.Reflection;
//
using AutoMapper;
//
using SimpleInjector;
using SimpleInjector.Packaging;

public class AutoMapperPackage : IPackage
{
    public void RegisterServices(Container container)
    {
        var profiles = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(x => typeof(AutoMapper.Profile).IsAssignableFrom(x));

        var config = new MapperConfiguration(cfg =>
        {
            foreach (var profile in profiles)
            {
                cfg.AddProfile(Activator.CreateInstance(profile) as AutoMapper.Profile);
            }
        });

        container.RegisterSingleton<MapperConfiguration>(config);
        container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));
    }
}

You would need to add the SimpleInjector.Packaging package, and then add a call to container.RegisterPackages(); in your bootstrap/configuration code.

Essentially, the only thing that really changes from StructureMap would be the last two lines.



回答3:

For mapping multiple IMapper's with different MapperConfiguration objects, which seems to be a somewhat recurring problem, I recommend the following approach, which doesn't even need refactoring the mapper method calls:

1) Create a generic wrapper around the IMapper interface. This wrapper can be either an Interface or Class, but obviously eventually you have to implement your wrapper, so I will show the concrete class below. Have this wrapper implement (or inherit, if you chose to make an Interface) the IMapper interface, like so:

public class ProfileMapper<TProfile> : IMapper where TProfile : Profile
{
    private IMapper mapper;
    private Profile profile;

    public ProfileMapper(TProfile profile)
    {
        this.profile = profile;
        this.mapper = new MapperConfiguration( cfg => cfg.AddProfile( this.profile ) )
                    .CreateMapper();
    }
}

The generic argument must be a subclass of "Profile", because from that profile is where you are gonna get your mapper configuration.

2) In this class, implement the IMapper interface simply by redirecting the calls to the private IMapper instance that you create in the constructor, like so:

public TDestination Map<TDestination>(object source)
{
    return mapper.Map<TDestination>( source );
}

3) Now you need to register in Simple Injector a partially closed instance of this ProfileMapper class for every Profile you have. You do this first by obtaining all classes which inherit from Profile, then creating this partially closed instance and then registering it. There are several ways of getting all the Profile classes, but I went with this:

    IEnumerable<Type> profileRegistrations =
                from type in profileAssembly.GetExportedTypes()
                where type.Namespace == "Namespace.Of.My.Profiles"
                where type.BaseType.Equals( typeof( Profile ) )
                select type;

    foreach (Type profileType in profileRegistrations)
    {
        Container.RegisterSingleton( profileType, profileType );
        Type mapperClosedType = typeof( ProfileMapper<> ).MakeGenericType( profileType );
        Container.RegisterSingleton( typeof( ProfileMapper<> ), mapperClosedType );
    }

This code first gets all Types that inherit from Profile, located in said namespace. Then for each Profile, I register it with SimpleInjector (not really necesary, since they are concrete Types and as such could be created by the container on-the-fly), then I make a partially closed instance of my ProfileWrapper class with the current Profile as a generic argument, and then finally I register my closed instance as a Singleton. In this way, you can create new Profiles without having to manually register new Wrappers.

And that's it. Now, instead of depending on and injecting the IMapper, you inject your ProfileWrapper with the Profile that you want to use, like this:

ProfileMapper<ApplicationProfile> appProfileMapper;
ProfileMapper<MvcProfile> mvcProfileMapper;
ProfileMapper<GuestProfile> guestProfile;

and so on. Each Wrapper was created using a separate MapperConfiguration using different profiles. Since the wrapper implements IMapper, all your mapping code stays the same. No need of refactoring the method calls, just the dependency types.

If you create a BaseProfile, just change the generic parameter in ProfileMapper to accept only instances fo this BaseProfile.