We integrate AutoMapper with DI containers using Unity as an example

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

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



Introducing AutoMapper, I think, is not necessary. You can discuss for a long time how convenient or not it is, on which tasks it is applicable, and on which - not, where it slows down, in the end; but in the end, you either use it or not. I enjoyed; and against the backdrop of more or less general convenience, I had a number of problems that degenerated into routine tasks. So, suppose you use AutoMapper too ...

Testing code using AutoMapper


The first thing that bothers with the active use of AutoMapper is that the main proposed use case assumes statics. See:

//конфигурация
Mapper.CreateMap();
//использование
OrderDto dto = Mapper.Map(order);

In this case, the configuration would need to be performed once per application, therefore, of course, it is taken somewhere to the area of ​​general initialization, 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 use unit testing, 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 in the program, and if these mappings themselves rely on external dependencies (more on this later), then you never know specifically how exactly these dependencies are initialized. In other words, you will find yourself in that very small adik that is prepared for people who use static dependencies in isolated testing.

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

Fortunately, a static Mapperone is just a facade, behind which, as a result, an object with an interface is hidden IMappingEngine. It remains only 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 not be difficult to transfer this solution to other containers)

So, after a short search on Stackoverflow and reading the sources, we generate approximately the following:

_configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
_configurationProvider.CreateMap();
//намылить, смыть, повторить
//это используем в контейнере или напрямую - как хотите
_mappingEngine = new MappingEngine(_configurationProvider);

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

var configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
configurationProvider.CreateMap(); //намылить, смыть, повторить
container.RegisterInstance(configurationProvider); //так мы можем всегда его достать из контейнера, например, для проверки корректности
container.RegisterInstance(new MappingEngine(configurationProvider));


It remains to add a mechanism that would allow mapping the configuration 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(configurationProvider);
container.RegisterInstance(new MappingEngine(configurationProvider));

Think a little - iteration 2:

var configurationProvider = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
//теперь не надо изобретать, откуда брать список профилей - просто зарегистрируем их все в контейнере
foreach (var profile in container.ResolveAll())
	configurationProvider .AddProfile(profile);
container.RegisterInstance(configurationProvider);
container.RegisterInstance(new MappingEngine(configurationProvider));


After the third repetition of this code, we moved it into the extension method, and two weeks later I made a nuget package so that I would never again be asked the question “how to do this”. Hurrah.

Using external dependencies when mapping


In fact, do not do so. No, seriously, don’t do this, mapping becomes too complicated, it needs to be tested separately, the whole idea is lost - in general, don’t do that. However, if you still need it (for example, you map from the DTO, in which the codes of reference values ​​are transmitted, to the database entity where the identifiers are needed, and you want to get the identifiers from the database by codes during mapping), then for this, oddly enough, there is also a standard mechanism.

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

private class OKEIResolver: ValueResolver
{
	private readonly Func _contextFactory;
	public OKEIResolver(Func contextFactory)
	{
		_contextFactory = contextFactory;
	}
	protected override int ResolveCore(string source)
	{
		using(var dc = _contextFactory())
		{
			//Обработка ошибок намеренно исключена
			return dc.Set()
					.Where(s => s.Code == source)
					.Single(s => s.Id)
		}
	}
}

Step 2 the big-eyed ones have already noticed for 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 this, ConstructServicesUsingit was necessary to call it before the first configuration using the resolver, otherwise it was created bypassing the container.

It should be noted that until the third (even, in my opinion, three-some) version of AutoMapper, using throw-in through the container was the only way (well, apart from the static facade) to get a mapper inside the resolver. Now, fortunately, inside ResolutionContextcomes a link to an existing mapper.

Needless to say, we also included this small but useful call into our extension method?

Comments, advice, corrections are welcome.

Also popular now: