📜 ⬆️ ⬇️

We integrate AutoMapper with DI-containers on the example of Unity

TL; DR: A package for easy registration (and configuration) of AutoMapper in Unity .

var container = new UnityContainer(); container.RegisterMappingProfile<DataModelToViewModel>(); container.RegisterMapper(); public SomeController(IMappingEngine mapper) { _mapper = mapper; } public ViewModel SomeAction() { return _mapper.Map<ViewModel>(dataModel) } 



I don’t need to introduce AutoMapper. You can discuss for a long time whether it is convenient or not, on which tasks it is applicable, and on which it is not, where it slows down, in the end; but in the end - you either use it or not. I enjoyed; and against the background of more or less general convenience, I had a number of problems that were transformed into routine tasks. So suppose you use AutoMapper too ...

Testing code using AutoMapper


The first thing that bothers you while actively using AutoMapper is that the main proposed usage scenario assumes statics. See:
')
 // Mapper.CreateMap<Order, OrderDto>(); // OrderDto dto = Mapper.Map<OrderDto>(order); 

At the same time, the configuration would have to be performed once per application, so it is naturally placed somewhere in the general initialization area, where it is lost (if you have modules in the application, each of which uses its own configuration, each of them has its own own configurator method, which is called from general initialization).

If you are using unit testing, then sooner (most likely sooner) or later you will encounter a situation where to test a piece of code you need to initialize an unknown number of mappings located in unknown places of the program, and if these mappings rely on external dependencies (more on this later), you never know exactly how these dependencies are initialized. In other words, you will find yourself in the very small adik that is prepared for people who use static dependencies for isolated testing.

The way out, in general, is obvious - let's find a way to turn a dependency from a static one into a regular one, and use a regular instance of an object (and it would be better - by interface), and manage its lifetime using the same tool that is used to manage life other dependencies.

Fortunately, the static Mapper is just a facade, behind which, as a result, an object with the IMappingEngine interface is IMappingEngine . It only remains to register it in our dependency injection container of choice, and cheers.

(I’ll make a reservation right away, we use Unity as a DI container, all the examples and the final solution are written for it, but it should be easy to transfer this solution to other containers)

So, after a brief search on Stackoverflow and reading the sources, we generate something like the following:

 _configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); _configurationProvider.CreateMap<Order, OrderDto>(); //, ,  //      -   _mappingEngine = new MappingEngine(_configurationProvider); 

It should be noted that in the first three, it seems, the approaches to this projectile mapper were created inside a specially written wrapper, and the wrapper hid the entire configuration in itself, and the wrapper was registered in DI. Like any extra level of abstraction, this one at some point also turned out to be superfluous, and we successfully got rid of it, bringing the configuration to the composition root region, and the mapper itself - directly to DI. Total we get:

 var configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); configurationProvider.CreateMap<Order, OrderDto>(); //, ,  container.RegisterInstance<IConfigurationProvider>(configurationProvider); //       , ,    container.RegisterInstance<IMappingEngine>(new MappingEngine(configurationProvider)); 


It remains to add a mechanism that would allow the mapping configuration to be removed from composition root. Again, first we made our bikes, and then, as expected, read the documentation. It turned out that the profile mechanism quite successfully solves our problem.

Iteration 1:

 var configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); configurationProvider.AddProfile(new SomeProfile()); //, ,  container.RegisterInstance<IConfigurationProvider>(configurationProvider); container.RegisterInstance<IMappingEngine>(new MappingEngine(configurationProvider)); 

We think a little - iteration 2:

 var configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers); //   ,     -       foreach (var profile in container.ResolveAll<Profile>()) configurationProvider .AddProfile(profile); container.RegisterInstance<IConfigurationProvider>(configurationProvider); container.RegisterInstance<IMappingEngine>(new MappingEngine(configurationProvider)); 


After the third repetition of this code, we took it out to an extension method, and after another two weeks I made a nuget-package in order to never again ask the question “how to do it”. Hooray.

Using external dependencies when mapping


Actually, don't do that. No, seriously, do not do that, mapping becomes too complicated, it needs to be tested separately, the whole idea is lost - well, don’t do it. However, if you still need it (for example, you map from DTO, which sends reference value codes, to the essence of the database, where identifiers are needed, and you want to retrieve identifiers from the database by codes during mapping), for this, oddly enough, there is also a standard mechanism.

Step 1: create a value resolver (a fully functional method, as a separate class, is not enough). For example:

 private class OKEIResolver: ValueResolver<string,int> { private readonly Func<ISomeDbContext> _contextFactory; public OKEIResolver(Func<ISomeDbContext> contextFactory) { _contextFactory = contextFactory; } protected override int ResolveCore(string source) { using(var dc = _contextFactory()) { //    return dc.Set<OKEI>() .Where(s => s.Code == source) .Single(s => s.Id) } } } 

Step 2 the big-eyed have already noticed themselves: we throw the dependence we need into the resolver.
Step 3: teach AutoMapper to create resolvers (and other useful things) from the container:
 configuration.ConstructServicesUsing(t => container.Resolve(t)); 

There is a trick: on the version of AutoMapper on which I implemented it, ConstructServicesUsing had to be called before the first configuration using a resolver, otherwise it was created bypassing the container.

It should be noted that right up to the third (even, in my opinion, three-some) version of AutoMapper, using a throw-in through the container was the only way (well, not counting the static facade) to get the mapper inside the resolver. Now, fortunately, inside the ResolutionContext comes a link to the existing mapper.

Need I say that this small but useful challenge was also included inside our extension method?

Comments, tips, corrections are welcome.

Source: https://habr.com/ru/post/244699/


All Articles