A friend of mine asked me yesterday how one would go about having an AutoMapper configuration such that you have multiple mappings for the same object pairs. This is an interesting situation for which I didn’t have an immediate answer. But, after playing around with AutoMapper a bit, my choice was to utilize a factory to provide multiple IMapper instances.
Simply put, a factory produces objects. In the AutoMapper case, we want different IMapper implementations based on some specification/parameter that will be passed into the factory’s “get” method. Of course, with the .NET Core DI, we could also possibly use Just-In-Time Dependency Injection, but it doesn’t appear to support parameterization. I also thought about defining an interface similar to the .NET Core logging (ILogger<T>) interface, but that didn’t seem like it would be that manageable for my purposes.
Diving right in, a factory for mappers might look something like the code below. It will hold a dictionary of all mappers that it is aware of and a string identifier for each mapper.
public interface IMapperFactory { IMapper GetMapper(string mapperName = ""); } public class MapperFactory : IMapperFactory { public Dictionary<string, IMapper> Mappers { get; set; } = new Dictionary<string, IMapper>(); public IMapper GetMapper(string mapperName) { return Mappers[mapperName]; } }
Pretty simple so far.
Next, we need to create all of our mappers and make the factory aware of them. I wanted to make this generic, so I use a bit of reflection to load all AutoMapper profiles and create a mapper per profile. As each mapper is created, it is added to the factory’s dictionary of mappers. Each of these profiles will inherit from the base AutoMapper Profile class. Since the AutoMapper Profile class supports a string-based identifier, this identifier will be used for the Mapper identifier too. I also go ahead and create a “default” mapper and register it with the DI in case anyone wants to simply inject IMapper. This effectively makes the code work in the case of using the factory, or not.
public void ConfigureServices(IServiceCollection services) { // Assume we have multiple Profile classes. We'll load them individually to create multiple mappers for our factory var mapperFactory = new MapperFactory(); IMapper defaultMapper = null; var assembly = this.GetType().GetTypeInfo().Assembly; var types = assembly.GetTypes().Where(x => x.GetTypeInfo().IsClass && x.IsAssignableFrom(x) && x.GetTypeInfo().BaseType == typeof(Profile)).ToList(); foreach (var type in types) { var profileName = string.Empty; var config = new AutoMapper.MapperConfiguration(cfg => { var profile = (Profile)Activator.CreateInstance(type); profileName = profile.ProfileName; cfg.AddProfile(profile); }); var mapper = config.CreateMapper(); mapperFactory.Mappers.Add(profileName, mapper); // If we still want normal functionality with a default injected IMapper if (defaultMapper == null) { defaultMapper = mapper; services.AddSingleton(defaultMapper); } }
Finally, we let the .NET Core DI know that we want our factory managed as a singleton.
services.AddSingleton<IMapperFactory>(mapperFactory); }
To test, we can utilize the factory directly after retrieving it through .NET Core’s DI.
public void Configure(IApplicationBuilder app, IApplicationLifetime appLifeTime, IHostingEnvironment env, ILoggerFactory loggerFactory) { var factory = app.ApplicationServices.GetService<IMapperFactory>(); var viewModel = new ViewModel() { Prop1 = "Test" }; // Map via our first mapper/profile var mapper = factory.GetMapper("FirstProfile.Api"); var mappedObj = mapper.Map<ViewModel, DomainModel>(viewModel); // Map via our second mapper/profile mapper = factory.GetMapper("SecondProfile.Api"); mappedObj = mapper.Map<ViewModel, DomainModel>(viewModel);