让Automapper使用Autofac和WebApi构建的服务

Hac*_*ese 8 c# dependency-injection autofac automapper asp.net-web-api

我正在使用WebAPI + Autofac + Automapper,以及一个用于数据访问的存储库.我需要将模型映射到我的域实体,具体来说,我需要将标识值转换为实际实体.没什么大不了的吧?我在MVC中做到了这一点没有问题.我将简化我正在做的事情来揭露要领.

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);
   }
}
Run Code Online (Sandbox Code Playgroud)

存储库在Autofac中注册,并且InstancePerApiRequest由于会话/事务管理而进行管理.所以,我需要在同一范围内注册我的转换器:

 builder.RegisterGeneric(typeof(EntityConverter<>))
        .AsSelf()
        .InstancePerApiRequest();
Run Code Online (Sandbox Code Playgroud)

Automapper配置看起来像:

 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();
Run Code Online (Sandbox Code Playgroud)

所以这里的部分很糟糕.我要了解Automapper要求在ConstructServicesUsing构建配置之前设置好.如果稍后设置,则不会使用它.上面的示例不起作用,因为container是根范围.如果我尝试解决EntityConverter<TestEntity>,Autofac会抱怨所请求的类型是注册到不同的范围,而你所在的类型不是它.有道理,我想要WebApi创建的范围.

让我暂停一下,并介绍一个关于WebApi依赖注入的事实(我真的不认为这是Autofac特有的).WebApi IDependencyScope为请求创建一个请求,并将其存储在HttpRequestMessage.Properties.除非我可以访问同一个HttpRequestMessage实例,否则我无法再次获取它.我的AsInstancePerApiRequest范围IRepository和我的转换器因此依赖于此IDependencyScope.

所以,这真的是问题的关键,我真的很沮丧与MVC的这种差异.你做不到

 cfg.ConstructServicesUsing(GlobalConfiguration.Configuration.DependencyResolver.GetService);
Run Code Online (Sandbox Code Playgroud)

这相当于使用container.Resolve.我不能用

 GlobalConfiguration.Configuration.DependencyResolver.BeginScope().GetService
Run Code Online (Sandbox Code Playgroud)

因为A)在我真正想要的那个旁边创建一个新的范围B)并没有真正让我清理我创建的新范围.使用Service Locator是一种遇到同样问题的新方法; 我无法进入WebApi正在使用的范围.如果我的转换器及其依赖项是单个实例或每个依赖项的实例,那么它不会是一个问题,但它们不是,所以它是,并且改变它会给我带来更多问题.

现在,我可以使用Autofac创建AutoMapper配置并将其注册为单个实例.我甚至可以创建每个请求IMappingEngine实例.但是,如果服务构造函数始终使用您在开头注册的单个委托,而该单个委托无法访问当前作用域,那对我没有任何好处.如果我可以根据每个映射引擎实例更改该委托,那么我可能会开展业务.但我不能.

那么什么能怎么办?

Jim*_*ard 6

另一个选项,这次它是内置的,是使用每个地图选项:

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

不要在映射配置中设置全局IoC配置.

另一种选择是使用您的IoC工具配置如何实例化MappingEngine:

public MappingEngine(
    IConfigurationProvider configurationProvider,
    IDictionary<TypePair, IObjectMapper> objectMapperCache,
    Func<Type, object> serviceCtor)
Run Code Online (Sandbox Code Playgroud)

第一个是Mapper.Configuration,第二个可能是单例,第三个可以用当前嵌套容器的分辨率填充.这样可以简化每次调用Map重载的过程.


Ale*_*tin 5

更新: Automapper已更新以支持该功能.见@Jimmy Bogard的回答

这个解决方案可能不是很好,但它的工作原理.解决方案与WebAPI 2有关,我不确定以前的版本.

在WebAPI 2中,您可以通过扩展方法IDependencyScope从当前获取当前数据.电流存储在当前的属性中.知道你的工厂看起来像:HttpRequestMessageGetDependencyScope()HttpRequestMessageItemsHttpContext

Mapper.Initialize(cfg =>
{
    cfg.ConstructServicesUsing(serviceTypeToConstruct =>
    {
        var httpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
        var currentDependencyScope = httpRequestMessage.GetDependencyScope();
        return currentDependencyScope.GetService(serviceTypeToConstruct);
    });
    // configure mappings
    // ...
});
Run Code Online (Sandbox Code Playgroud)