Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having Automapper use services constructed by a Autofac with WebApi

I'm using WebAPI + Autofac + Automapper, with a repository for data access. I need to map a model to my domain entities, specifically, I need to convert an identity value to the actual entity. No big deal, right? I've done this in MVC with no problem. I will simplify what I am doing to expose the essentials.

public class EntityConverter<T> : ITypeConverter<int, T>
        where T : Entity
{
   public EntityConverter(IRepository<T> repository)
   {
       _repository = repository;
   }

   private readonly IRepository<T> _repository;

   public T Convert(ResolutionContext context)
   {
       _repository.Get((int) context.SourceValue);
   }
}

Repositories are registered with Autofac, and are managed as InstancePerApiRequest because of session/transaction management. So, I need to register my converter in that same scope:

 builder.RegisterGeneric(typeof(EntityConverter<>))
        .AsSelf()
        .InstancePerApiRequest();

The Automapper config looks something like:

 var container = builder.Build(); // build the Autofac container and do what you will

 Mapper.Initialize(cfg => {
      cfg.ConstructServicesUsing(container.Resolve); // nope nope nope
      // configure mappings
      cfg.CreateMap<int, TestEntity>().ConvertUsing<EntityConverter<TestEntity>>()
});
Mapper.AssertConfigurationIsValid();

So here's the part that sucks. I am to understand Automapper requires the ConstructServicesUsing guy to be set before you build your config. If you set it later, it won't be used. The example above won't work because container is the root scope. If I try and resolve EntityConverter<TestEntity>, Autofac will complain that the requested type is registered for a different scope, and the one you're in ain't it. Makes sense, I want the scope created by WebApi.

Let me pause a sec and cover one fact about WebApi dependency injection (I don't really think this is Autofac-specific). WebApi creates an IDependencyScope for the request, and stashes it in the HttpRequestMessage.Properties. I can't get it back again unless I have access to that same HttpRequestMessage instance. My AsInstancePerApiRequest scoping on IRepository and my converter thus rely on that IDependencyScope.

So, that's really the meat and potatoes of the problem, and I really frustrated with this difference from MVC. You can't do

 cfg.ConstructServicesUsing(GlobalConfiguration.Configuration.DependencyResolver.GetService);

That's equivalent to using container.Resolve. I can't use

 GlobalConfiguration.Configuration.DependencyResolver.BeginScope().GetService

because A) that creates a new scope next to the one I actually want B) doesn't really let me clean up the new scope I created. Using Service Locator is a new way to have the same problem; I can't get to the scope WebApi is using. If my converter and its dependencies were single instance or instance per dependency, it wouldn't be a problem, but they aren't, so it is, and changing that would create lots more problems for me.

Now, I can create AutoMapper config with Autofac and register it as a single instance. I can even create per-request IMappingEngine instances. But that doesn't do me any good if the service constructor always uses that single delegate you register at the beginning, which has no access to the current scope. If I could change that delegate per each mapping engine instance, I might be in business. But I can't.

So what can I do?

like image 967
moribvndvs Avatar asked Sep 11 '13 04:09

moribvndvs


People also ask

When should you not use AutoMapper?

If you have to do complex mapping behavior, it might be better to avoid using AutoMapper for that scenario. Reverse mapping can get very complicated very quickly, and unless it's very simple, you can have business logic showing up in mapping configuration.

What is the use of AutoMapper?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.

Is AutoMapper a singleton?

Your configuration (e.g. Automapper Profiles) are singletons. That is, they are only ever loaded once when your project runs.


1 Answers

Another option, this time it's built-in, is to use the per-map options:

Mapper.Map<Source, Destination>(dest, opt => opt.ConstructServicesUsing(type => Request.GetDependencyScope().GetService(typeof(YourServiceTypeToConstruct))));

Don't bother with setting up the global IoC config in your mapping configuration.

Another option is to use your IoC tool to configure how to instantiate the MappingEngine:

public MappingEngine(
    IConfigurationProvider configurationProvider,
    IDictionary<TypePair, IObjectMapper> objectMapperCache,
    Func<Type, object> serviceCtor)

The first one is just Mapper.Configuration, the second should probably be a singleton, and the third you can fill in with the current nested container's resolution. This would simplify from having to call the Map overload every time.

like image 159
Jimmy Bogard Avatar answered Oct 16 '22 16:10

Jimmy Bogard