在AutoMapper中为自定义类注入依赖



我有一个实体和Dto,并试图自定义映射这些类有EmpUrl=baseurl+EmpPath。我计划通过DI从配置文件和映射中获得baseurl,但得到以下错误:

系统。不能动态创建"MappingProfile"类型的实例。原因:没有定义无参数构造函数

public MyMappingProfile(IOptions <ApplicationSettings> applicationsettings)
{
CreateMap<Emp, EmpDto>()
.ForMember(e => e.EmpUrl, e => e.MapFrom(src => applicationsettings.Value.BaseDomain + src.EmpPath));

}

要注册依赖的类在下面:

public class MyModule : Autofac.Module
{
private readonly IConfiguration Configuration;
public ApplicationModule(IConfiguration configuration)
{
Configuration = configuration;

}
protected override void Load(ContainerBuilder builder)
{
// Register Automapper profiles
var config = new MapperConfiguration(cfg => { cfg.AddMaps(typeof(MappingProfile).Assembly); });

config.AssertConfigurationIsValid();
builder.Register(c => config)
.AsSelf()
.SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();

var settings = new ApplicationSettings();
Configuration.Bind(settings);
builder.RegisterInstance(Options.Create(settings));
}

}

Startup.cs:

public void ConfigureContainer(ContainerBuilder container)
{
container.RegisterModule(new ApplicationModule(Configuration));                

}

我在这里做错了什么?

假设MyMappingProfile继承自AutoMapper.Profile类,这里有几个问题:

  1. 在构建MapperConfiguration实例期间立即调用cfg.AddMaps方法(根据AutoMapper v11.0.1源代码)。这是在ApplicationModule的应用程序启动加载期间(而不是在运行时从容器惰性解析依赖关系期间)。AddMaps调用创建一个新的MyMappingProfile实例,甚至在ApplicationSettings从配置中绑定并添加到DI容器之前执行。
  2. 可能与#1相关,cfg.AddMaps方法调用使用。netSystem.Activator.CreateInstance直接实例化映射配置文件(例如MyMappingProfile),并且不传递任何参数,这就是为什么你得到"没有参数的构造函数定义";错误。这个流不使用传递给MapperConfiguration.CreateMapper方法的serviceCtor参数。(AutoMapperMapperConfigurationExpression.AddMaps调用AddMapsCore,后者在v11.0.1源中调用AddProfile(Type))

使用AutoMapper在运行时从DI容器解析IOptions<ApplicationSettings>的一种方法是使用传递给MapperConfiguration.CreateMapper的服务工厂方法(serviceCtor)。

映射配置文件可以是这样的:

public class MyMappingProfile : Profile
{
public MyMappingProfile()
{
CreateMap<Emp, EmpDto>()
.ForMember(dest => dest.EmpUrl, e => e.MapFrom((src, dest, destMember, ctx) =>
{
if (string.IsNullOrWhiteSpace(src.EmpPath))
{
return null;
}
var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));
return applicationSettings.Value.BaseDomain + src.EmpPath;
}));
}
}

为了保持映射干净或重用逻辑,您可以将内联MapFrom逻辑提取到映射配置文件类中的本地方法中,使用opt.MapFrom的单独自定义值解析器(或成员值解析器),或使用opt.ConvertUsing的单独自定义值转换器。自定义转换器或解析器可以使用通过构造函数注入的依赖项,而不是显式地使用ResolutionContext

如果您只尝试更改映射配置文件,那么Autofac将抛出一个ObjectDisposedException,其中包含以下行This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.的消息:

var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));

的解决方案是改变你的ApplicationModule.Load

builder.Register(c => c.Resolve<MapperConfiguration>()
.CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();

builder.Register(c =>
{
var ctx = c.Resolve<IComponentContext>();
var mapperConfig = c.Resolve<MapperConfiguration>();
return mapperConfig.CreateMapper(ctx.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();

这是基于个人经验,也涵盖了其他SO问题,如此解决操作已经结束。Autofac, Automapper &;IMemberValueResolver。Dennis Doomen在《Autofac中出现死锁的奇怪案例》中很好地解释了Autofac设计的细微差别。

总而言之,使用临时容器来解决构造时所需的依赖关系,但使用全局容器来解决任何运行时依赖关系。

另一个软件设计方案是根本不从AutoMapper配置文件中的DI容器中解析ApplicationSettings。相反,调用IMapper.Map的类可以通过构造函数注入注入IOptions<ApplicationSettings>,然后就地转换EmpDto.EmpUrl,或者添加一个单独的属性,如EmpDto.EmpFullUrl,将基本域前缀添加到通过AutoMapper从Emp.EmpPath逐字映射的EmpDto.EmpPath属性。

相关内容

  • 没有找到相关文章

最新更新