我正在检查一个使用
AutoMapper
和 Microsoft.Extensions.DependencyInjection
的项目。全部
映射器按预期工作,但对于其中一个 - 唯一 - 它使用自定义类型转换器。
// there is some abstractions here, but MapperProfileProvider inherits AutoMapper.Profile
public class Mappings : MapperProfileProvider
{
public Mappings()
{
CreateMap<MyItemTypeA, MyItemTypeB>().ConvertUsing<MyItemTypeConverter>();
CreateMap<MyItemTypeB, MyItemTypeA>().ConvertUsing<MyItemTypeConverter>();
}
public class MyItemTypeConverter : ITypeConverter<MyItemTypeA, MyItemTypeB>,
ITypeConverter<MyItemTypeB, MyItemTypeA>
{
public MyItemTypeB Convert(MyItemTypeA source, MyItemTypeB destination, ResolutionContext context)
{
// do the conversion and return
return new MyItemTypeB()
{
// assignments...
};
}
public MyItemTypeA Convert(MyItemTypeB source, MyItemTypeA destination, ResolutionContext context)
{
// do the conversion and return
return new MyItemTypeA()
{
// assignments...
};
}
}
}
映射器被注入到使用它的类中。
internal class MyApiService : ApiServiceBase
{
private readonly IMapper _mapper;
public MyApiService(IMapper mapper) { _mapper = mapper; }
public async Task<object> Post(MyRequest request)
{
// here the exception occurs
var myItemA = _mapper.Map<MyItemTypeA>(request.ItemB);
}
}
使用自定义类型映射器会抛出异常
AutoMapperMappingException: Cannot create an instance of type MyTypeConverter
我几乎可以肯定这个问题与 DI 有关。 DI 不知道和/或无法创建类型转换器的实例。这个答案为我指明了方向:
DI 不知道您的自定义解析器类型,因此它甚至不会尝试。 由于您没有使用 DI 包的扩展方法,因此解析器不会添加到服务集合中。如果需要,您可以手动添加这些服务
除了手动添加服务外,答案建议使用
services.AddAutoMapper
。这就是我正在做的事情。 AddAutoMapper
代码如下所示:
builder.Services.AddAutoMapper((IServiceProvider f, IMapperConfigurationExpression cfg) =>
{
foreach (var profile in f.GetService<IEnumerable<IMapperProfileProvider>>())
{
cfg.AddProfile((Profile)profile);
}
}, new Assembly[] { });
(这里也有一些抽象。但我相信它符合docs中的示例)
我尝试过的:
当使用
MyItemTypeConverter
显式添加 services.AddScoped<MyTypeConverter>();
作为服务时,映射有效。
我还从 AutoMapper docs
找到了以下内容并向 AutoMapper 提供自定义类型转换器的实例,或者简单地提供 AutoMapper 将在运行时实例化的类型。上述源/目标类型的映射配置将变为:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<string, int>().ConvertUsing(s => Convert.ToInt32(s));
cfg.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
cfg.CreateMap<string, Type>().ConvertUsing<TypeTypeConverter>();
cfg.CreateMap<Source, Destination>();
});
第 3 行引起了我的注意,它为 AutoMapper 提供了一个实例。如果我使用它,它也有效。
CreateMap<MyItemTypeA, MyItemTypeB>().ConvertUsing(new MyItemTypeConverter());
CreateMap<MyItemTypeB, MyItemTypeA>().ConvertUsing(new MyItemTypeConverter());
因此,提供实例或添加类型转换器作为服务即可。我无法理解它。为什么类型转换器不能在运行时实例化?文档表明这是可能的(“...或者只是 AutoMapper 将在运行时实例化的类型)。为什么 DI 不知道自定义类型转换器?我错过了什么?
类型转换器未在运行时实例化的原因是它位于不同的程序集中,因此未找到。现在很清楚了...
https://docs.automapper.org/en/stable/Dependency-injection.html#asp-net-core
您可以使用配置文件定义配置。然后,您可以通过在启动时调用 IServiceCollection 扩展方法 AddAutoMapper 让 AutoMapper 知道这些配置文件在哪些程序集中定义。
我终于使用了这个覆盖
public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action<IServiceProvider, IMapperConfigurationExpression> configAction, params Assembly[] assemblies)
=> AddAutoMapperClasses(services, configAction, assemblies);
并将程序集传递给它
var assemblies = new Assembly[]
{
typeof(typeFromAssembly1).Assembly,
typeof(typeFromAssembly2).Assembly
};
builder.Services.AddAutoMapper((IServiceProvider f, IMapperConfigurationExpression cfg) =>
{
foreach (var profile in f.GetService<IEnumerable<IMapperProfileProvider>>())
{
cfg.AddProfile((Profile)profile);
}
}, assemblies);