如何在 c# AutoMapper 操作中使用服务

How to use services in a c# AutoMapper Action

提问人:Jimmyt1988 提问时间:11/17/2023 最后编辑:Jimmyt1988 更新时间:11/21/2023 访问量:91

问:

我设置了一个配置文件:

builder.Services
    .AddAutoMapper((IMapperConfigurationExpression a) => {
        a.AddProfile<GeneralMappingProfile>();
    });

以下是简介:

public class GeneralMappingProfile : Profile
{
    public GeneralMappingProfile()
    {
        CreateMap<Source, Destination>()
            .AfterMap<SourceToDestinationAction>()
            .ForAllMembers(x => x.Ignore());
    }
}

我的操作如下所示:

public class SourceToDestinationAction : IMappingAction<Source, Destination>
{
    private readonly IACoolService coolService;

    public SourceToDestinationAction(IACoolService _coolService){
        coolService = _coolService;
    }

    public void Process(Source source, Destination destination, ResolutionContext context)
    {
        destination.Title = coolService.MakeMultiLanguageValue(source.Title);
    }
}

我想像这样使用它:

...(IMapper mapper){
    ...
    Destination destinationData = mapper.Map<Destination>(sourceData);
}

这不起作用,因为 Action 需要无参数构造函数。

如何在我的操作中建立依赖关系?

系统信息:

  • .净6.0
  • 自动映射器 12.0.1
  • AutoMapper.Extensions.Microsoft.DependencyInjection
  • Web 应用程序

审核注意事项:

  • 请注意,这不是配置文件的 DI,而是操作。

上下文信息:

  • 我正在尝试在单元测试中测试映射:

    MapperConfiguration config = new (cfg =>
    {
        GeneralMappingProfile profile = new();
        cfg.AddProfile(profile);
    });
    mapper = config.CreateMapper();
    mapper.Map(item, content); // Throws error here as SourceToDestinationAction has no paramterless constructor
    

抛出的错误:

无法动态创建类型为“SourceToDestinationAction”的实例。原因:未定义无参数构造函数。

C# 自动映射器

评论

1赞 Progman 11/18/2023
这回答了你的问题吗?如何在 AutoMapper 配置文件类中注入服务
0赞 Jimmyt1988 11/20/2023
很遗憾没有。这是注入到配置文件类中的方法。我想注入到 Action 类中。
0赞 nvoigt 11/20/2023
您使用 AutoMapper 而不是将“IMappingAction<Source, Destination>直接注入到需要映射的任何位置是否有特定原因?似乎您只是“使用”Automapper来获取此依赖项,如果您直接注入它,则DI已经处理了它。
0赞 Jimmyt1988 11/20/2023
我假设您可以通过 IMapper 使用 Map<Source, Dest> 函数,但我会尝试使用 IMappingAction<Source, Dest>。处理并查看 DI 此时是否有效。谢谢。包涵。
0赞 Jimmyt1988 11/20/2023
不幸的是,这并没有太大帮助,因为它在针对 Process 运行时需要 ReqestContext

答:

0赞 nvoigt 11/20/2023 #1

我不确定您使用的是什么版本,但文档说您确实可以做到这一点。这句话看起来和你想做的一模一样:

Asp.Net Core 和 AutoMapper.Extensions.Microsoft.DependencyInjection

如果您使用的是 Asp.Net Core 和 AutoMapper.Extensions.Microsoft.DependencyInjection 包,这也是使用依赖项注入的好方法。不能将依赖项注入 Profile 类,但可以在 IMappingAction 实现中执行此操作。

以下示例演示如何利用依赖项注入,将访问当前 HttpContext 的 IMappingAction 连接到映射操作后的 Profile:

public class SetTraceIdentifierAction : IMappingAction<SomeModel, SomeOtherModel>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public SetTraceIdentifierAction(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public void Process(SomeModel source, SomeOtherModel destination, ResolutionContext context)
    {
        destination.TraceIdentifier = _httpContextAccessor.HttpContext.TraceIdentifier;
    }
}

public class SomeProfile : Profile
{
    public SomeProfile()
    {
        CreateMap<SomeModel, SomeOtherModel>()
            .AfterMap<SetTraceIdentifierAction>();
    }
}

评论

0赞 Jimmyt1988 11/20/2023
我正在使用 AutoMapper 12.0.1 和 .Net6.0. 当我这样做时,我得到“操作需要一个无参数的构造函数”。
0赞 Doug Moore 12/1/2023
@Jimmyt1988我有同样的问题..完全按照文档进行操作。唯一的区别是我在网上 7
1赞 Progman 11/21/2023 #2

问题归结为这样一个事实,即服务集合/服务提供者不知道如何构建实例,无论是否使用 AutoMapper。即使将所有依赖项都添加到服务集合中,它仍然(显然)不知道如何构建对象,如以下代码所示:SourceToDestinationAction

IServiceCollection services = new ServiceCollection();
services.AddSingleton<IACoolService, MyCoolService>();
//services.AddSingleton<SourceToDestinationAction>();

IServiceProvider provider = services.BuildServiceProvider();
provider.GetRequiredService<SourceToDestinationAction>();

这将导致消息“类型'(命名空间)没有服务”。SourceToDestinationAction' 已注册。InvalidOperationException

但是,当您使用 // 之一将类型注册到服务集合时,它可以毫无问题地解析该类型。AddSingleton()AddScoped()AddTransient()

IServiceCollection services = new ServiceCollection();
services.AddSingleton<IACoolService, MyCoolService>();
services.AddSingleton<SourceToDestinationAction>();

IServiceProvider provider = services.BuildServiceProvider();
var act = provider.GetRequiredService<SourceToDestinationAction>();
Console.WriteLine(act); // prints "(namespace).SourceToDestinationAction"

之后,AutoMapper 最终也将能够解析该类型。

解决方案是(显式)在服务集合中注册您的类,至少使用 .事实上,当您将程序集实例用作参数时,例如SourceToDestinationActionAddTransient()AddAutoMapper()

services.AddAutoMapper(typeof(GeneralMappingProfile).Assembly);

它将自动为您注册所有此类操作,如 ServiceCollectionExtensions.AddAutoMapperClasses 的源代码所示:

var openTypes = new[]
{
    typeof(IValueResolver<,,>),
    typeof(IMemberValueResolver<,,,>),
    typeof(ITypeConverter<,>),
    typeof(IValueConverter<,>),
    typeof(IMappingAction<,>)
};
foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes().Where(type => type.IsClass && !type.IsAbstract &&
             Array.Exists(openTypes, openType => type.GetGenericInterface(openType) != null))))
{
    // use try add to avoid double-registration
    services.TryAddTransient(type);
}

也许服务集合/服务提供程序可以这样配置,即在未注册类型时自动回退。transient

评论

0赞 Lucian Bargaoanu 11/21/2023
显然,主要用例是简单地传递所需的程序集,并让扩展处理所有事情。你可以做的其他事情,这只是额外的灵活性:)
0赞 Jimmyt1988 11/23/2023
该问题似乎源于框架与构造函数中具有参数的 Action 不兼容。但是,我会一有时间就调查。我已经迁移到我自己的单独课程,这些课程使用普通的 DI 来缓解我在截止日期内工作的问题。感谢您提供此信息。
0赞 Progman 11/23/2023
@Jimmyt1988 您可以简单地使用类似的东西注册您的操作,它应该能够创建类型的实例。services.AddSingleton<SourceToDestinationAction>();SourceToDestinationAction