【问题标题】:Asp Net Core DI for AutoMapper用于 AutoMapper 的 Asp Net Core DI
【发布时间】:2023-03-17 06:34:01
【问题描述】:

我有一个关于 ASP NET Core 中 AutoMapper 依赖注入的问题。我知道在实现自定义IValueResolverIMemberValueResolver 时,可以使用 DI 的自动映射器扩展。这仅在 AutoMapper/DI 创建自定义值解析器时才有效。不幸的是,如果我需要手动创建值解析器,这将不起作用。另请注意,当我调用mapper.Map<>() 方法时,我不想传递项目,因为我不希望IMapper 的使用者在运行时知道任何额外的参数

考虑下面的代码:

class Entity1
{
    public int MyProperty1 { get; set; } 
}

class Dto1 
{
    public int MyProperty1 { get; set; } 
    public string CustomProperty { get; set; } 
}

在 MyProfile.cs 中

CreateMap<Entity1, Dto1>()
    .ForMember(x => x.CustomProperty, 
       opt => opt.ResolveUsing(new MyCustomPropertyResolver("Important value")));

而 MyCustomPropertyResolver 是这样的:

public class MyCustomPropertyResolver : IValueResolver<Entity1, Dto1, string>
{
    string _someValue;
    public MyCustomPropertyResolver(string someValue)
    {
        _someValue = someValue;
    }
    string Resolve(Entity1 source, Dto1 destination, string destMember, ResolutionContext context)
    {
        //I need IHttpContextAccessor ..... How can I do that???
    } 
}

当然,如果我这样做:

CreateMap<Entity1, Dto1>()
    .ForMember(x => x.CustomProperty, 
       opt => opt.ResolveUsing<MyCustomPropertyResolver>()));

我可以使用 DI 并将 IHttpContextAccessor 添加到 MyCustomPropertyResolver 构造函数,但是我将无法将任何额外的参数传递给解析器,这些参数对于解析实际值也很重要。有没有办法做到这一点?我能够实现这一点的唯一方法是在MyProfile 类上添加一个静态属性,并通过服务请求的控制器上的ActionFilter 设置它。虽然这个工作,但我不喜欢这个解决方案,因为它会创建不需要的依赖项。 AutoMapper 上的官方解决方案是这样做的:

// This solution is not good enough for my need
var dto = mapper.Map<Dto1>(entity, opt => { opt.Items["AnyThing"] = Whatever; }); 

使用上面的代码,我可以将HttpContext 传递给项目字典,但这将使映射器使用者需要传递可能不知道的参数。

我的代码实际上要复杂得多,但它在概念上使用了上面相同的示例。

【问题讨论】:

标签: c# dependency-injection asp.net-core automapper


【解决方案1】:

AutoMapper.Extensions.Microsoft.DependencyInjection 将自动注册从IValueResolver(和一些其他接口)继承的任何具有瞬态范围的服务。 因此,您需要确保您的自定义解析器具有默认构造函数(否则,如果您的唯一构造函数采用字符串参数,则当 AutoMapper 尝试自动注册您的自定义解析器时会出错)。

public MyCustomPropertyResolver()
{
}

然后,您可以使用调用非默认构造函数(使用字符串参数)的实际实例创建地图:

CreateMap<Source, Destination>()
.ForMember(x => x.CustomProperty, 
   opt => opt.MapFrom(new MyCustomPropertyResolver("Important value")));

很遗憾,由于您没有使用 DI 来实例化自定义解析器,因此 DI 将无法将其他服务直接注入自定义解析器。解决方法是将这些服务注入解析器的消费者,例如控制器,并通过 Items 集合将它们传递给解析器:

_mapper.Map<Destination>(source, opts => { opts.Items["MyService"] = _myServiceInstance; });

编辑 1:

对于更优雅的解决方案,您的自定义解析器可以利用完全依赖注入并在构造函数中采用字符串参数,请考虑以下事项:

您的自定义解析器可能如下所示:

public class MyCustomPropertyResolver : IValueResolver<Source, Destination, string>
{
    private readonly MyService _myService;
    // plus others you need/have registered

    // still need at least one constructor without the string!
    public MyCustomPropertyResolver(MyService _myService)
    {
        _myService = myService;
    }

    public MyCustomPropertyResolver(MyService myService, string fieldName)
    {
        _myService = myService;

        // do something important with fieldName
    }

    // rest excluded
}

为您的自定义解析器创建一个工厂类:

public class MyCustomPropertyResolverFactory
{
    static internal IServiceProvider _provider;
    public static void Configure(IServiceProvider serviceProvider)
    {
        _provider = serviceProvider;
    }

    public static MyCustomPropertyResolver CreateResolverFor(string importantValue)
    {
        return ActivatorUtilities.CreateInstance<MyCustomPropertyResolver>(_provider, importantValue);
    }
}

在 Startup.cs 中配置

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    MyCustomPropertyResolverFactory.Configure(app.ApplicationServices);

    // rest excluded
}

在你的地图中使用它

CreateMap<Source, Destination>()
.ForMember(x => x.CustomProperty, 
   opt => opt.MapFrom(MyCustomPropertyResolverFactory.CreateResolverFor("Important value")));

当然你可以让工厂完全通用:

public class MyFactory
{
    static internal IServiceProvider _provider;
    public static void Configure(IServiceProvider serviceProvider)
    {
        _provider = serviceProvider;
    }

    public static T Create<T>(params object[] parameters)
    {
        return ActivatorUtilities.CreateInstance<T>(_provider, parameters);
    }
}

然后这样使用:

.ForMember(d => d.MyProperty, opt => opt.MapFrom(MyFactory.Create<MyResolver>("some string or any number of other parameters")))

【讨论】:

  • 那是错误的。解析器将通过 DI 创建,因此它可以注入 DI 引擎已知的任何内容。
  • 当然,如果它是另一个服务,但如果构造函数像 OP 的问题一样采用字符串参数,请继续尝试。
  • 是的,但这是 DI 的普遍问题,与 AM 几乎没有关系。而且您的回答充其量仍然具有误导性。你总是可以重写它..
  • 而且他希望同时注入价值和服务。
  • 正如 OP 所指出的,可以将附加服务注入控制器并通过 IMappingOperationOptions.Items 传递到自定义解析器,例如_mapper.Map(source, opts => { opts.Items["MyService"] = _myServiceInstance; });
猜你喜欢
  • 2021-07-26
  • 2021-06-03
  • 1970-01-01
  • 2021-06-11
  • 2021-04-14
  • 2021-03-04
  • 2023-03-08
  • 2017-07-28
  • 1970-01-01
相关资源
最近更新 更多