我有一个实体和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
类,这里有几个问题:
- 在构建
MapperConfiguration
实例期间立即调用cfg.AddMaps
方法(根据AutoMapper v11.0.1源代码)。这是在ApplicationModule
的应用程序启动加载期间(而不是在运行时从容器惰性解析依赖关系期间)。AddMaps
调用创建一个新的MyMappingProfile
实例,甚至在ApplicationSettings
从配置中绑定并添加到DI容器之前执行。 - 可能与#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
属性。