I have an AutoMapper profile which currently depends on UrlHelper for the following mapping:
CreateMap<Post, OpenGraphModel>().ForMember(
m => m.Title,
x => x.MapFrom(p => p.Link.Title)
).ForMember(
m => m.Description,
x => x.MapFrom(p => p.Link.Description)
).ForMember(
m => m.Image,
x => x.MapFrom(p => p.Link.Picture)
).ForMember(
m => m.Url,
x => x.MapFrom(p => urlHelper.RouteUrl("PostShortcut", new { id = p.Id }, "http"))
);
This was fine up until when I wanted to reuse my AutoMapper profile in a context that’s outside of a web request.
I can think of three solutions for this, none of which really convinces me (no particular order):
-
Remove the
UrlHelperdependency altogether from the mapping profile.
This would mean I now have to manually map theUrlproperty ofOpenGraphModel, which kind of defeats the purpose of usingAutoMapper, in my opinion. I like my mappers being able to set all the properties I need in the destination object. -
Remove this mapping profile from non-web contexts, since non-web request context shouldn’t be mapping to view models directly anyways.
In order to accomplish this, I would have to make myIMapperinstances either transient, per web request (or thread), instead of singleton, which introduces additional overhead I do not want to introduce. -
A third option is to not use the
IMapperimplementation on non-web request contexts (i.e. jobs), but this seems pretty much out of the question, since I’m reusing components which end up requiring me to use the mapper.
I guess having two singleton mappers would be the one that makes most sense (which choose profiles based on the context), but I don’t know how I should do that, here’s my AutoMapper installer in it’s current form:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
using AutoMapper.Mappers;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
namespace Common
{
internal sealed class AutoMapperInstaller : IWindsorInstaller
{
private readonly Type[] profileTypes;
public AutoMapperInstaller(params Type[] profileTypes)
{
if (profileTypes == null)
{
throw new ArgumentNullException("profileTypes");
}
this.profileTypes = profileTypes;
}
public void Install(IWindsorContainer container, IConfigurationStore store)
{
IEnumerable<Assembly> assemblies = profileTypes.Select(t => t.Assembly).ToList();
foreach (Assembly assembly in assemblies)
{
container.Register(
AllTypes
.FromAssembly(assembly)
.BasedOn(typeof (ITypeConverter<,>))
.WithServiceSelf()
);
}
foreach (Assembly assembly in assemblies)
{
container.Register(
Classes
.FromAssembly(assembly)
.BasedOn<Profile>()
.LifestyleTransient()
);
}
container.Register(
Component
.For<ITypeMapFactory>()
.ImplementedBy<TypeMapFactory>()
.LifestyleTransient()
);
container.Register(
Component
.For<IConfiguration, IConfigurationProvider>()
.UsingFactoryMethod(InstanceConfigurationStore)
.LifestyleTransient()
);
container.Register(
Component
.For<IMappingEngine>()
.ImplementedBy<MappingEngine>()
.LifestyleTransient()
);
container.Register(
Component
.For<IMapper>()
.ImplementedBy<Mapper>()
.DynamicParameters(
(k, parameters) => parameters["profileTypes"] = profileTypes
)
.LifestyleSingleton()
);
}
private ConfigurationStore InstanceConfigurationStore(IKernel kernel)
{
ITypeMapFactory typeMapFactory = kernel.Resolve<ITypeMapFactory>();
IEnumerable<IObjectMapper> mappers = MapperRegistry.AllMappers();
return new ConfigurationStore(typeMapFactory, mappers);
}
}
}
Resolved by mocking the
RequestContextpassed toUrlHelperin non-web request contexts.