AutoMapper 中用于自定义的依赖注入 类
Dependency injection in AutoMapper for custom classes
我有一个实体和 Dto 并尝试自定义映射那些 classes 以具有 EmpUrl=baseurl+EmpPath.I 我计划从配置文件中获取 baseurl 并通过 DI 进行映射但出现以下错误:
System.MissingMethodException: 无法动态创建类型 'MappingProfile' 的实例。原因:没有定义无参数构造函数。
public MyMappingProfile(IOptions <ApplicationSettings> applicationsettings)
{
CreateMap<Emp, EmpDto>()
.ForMember(e => e.EmpUrl, e => e.MapFrom(src => applicationsettings.Value.BaseDomain + src.EmpPath));
}
注册依赖的class如下:
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
class,这里有几个问题:
-
cfg.AddMaps
方法在构造 MapperConfiguration
实例期间立即调用(根据 AutoMapper v11.0.1 source)。这是在 ApplicationModule
的应用程序启动加载期间(不是在运行时从容器延迟解析依赖项期间)。 AddMaps
调用创建了 MyMappingProfile
的新实例,甚至在 ApplicationSettings
从配置绑定并添加到 DI 容器之前执行。
- 可能与#1 相关,
cfg.AddMaps
方法调用直接使用 .NET System.Activator.CreateInstance
来实例化映射配置文件(例如 MyMappingProfile
)并且不传递任何参数,即为什么会收到“未定义无参数构造函数”错误。此流程不使用传递给 MapperConfiguration.CreateMapper
方法的 serviceCtor
参数。 (AutoMapper MapperConfigurationExpression.AddMaps
调用 AddMapsCore
,每个 v11.0.1 source 调用 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
逻辑提取到映射配置文件 class 中的本地方法中,这是一个单独的自定义 Value Resolver (or Member Value Resolver) used with opt.MapFrom
, or a separate custom Value Converter 用于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 问题中,例如 This resolve operation has already ended. Autofac, Automapper & IMemberValueResolver. Dennis Doomen gives a good explanation of this Autofac design nuance in The curious case of a deadlock in Autofac。
So to summarize, use the temporary container for resolving dependencies needed at construction time, but use the global container to resolve any run-time dependencies.
另一种软件设计替代方案是根本不解析 AutoMapper 配置文件内的 DI 容器中的 ApplicationSettings
。相反,调用 IMapper.Map
的 class 可以通过构造函数注入注入 IOptions<ApplicationSettings>
,然后就地转换 EmpDto.EmpUrl
或添加单独的 属性,如 EmpDto.EmpFullUrl
将基本域前缀添加到 EmpDto.EmpPath
属性 通过 AutoMapper 从 Emp.EmpPath
逐字映射。
我有一个实体和 Dto 并尝试自定义映射那些 classes 以具有 EmpUrl=baseurl+EmpPath.I 我计划从配置文件中获取 baseurl 并通过 DI 进行映射但出现以下错误:
System.MissingMethodException: 无法动态创建类型 'MappingProfile' 的实例。原因:没有定义无参数构造函数。
public MyMappingProfile(IOptions <ApplicationSettings> applicationsettings)
{
CreateMap<Emp, EmpDto>()
.ForMember(e => e.EmpUrl, e => e.MapFrom(src => applicationsettings.Value.BaseDomain + src.EmpPath));
}
注册依赖的class如下:
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
class,这里有几个问题:
-
cfg.AddMaps
方法在构造MapperConfiguration
实例期间立即调用(根据 AutoMapper v11.0.1 source)。这是在ApplicationModule
的应用程序启动加载期间(不是在运行时从容器延迟解析依赖项期间)。AddMaps
调用创建了MyMappingProfile
的新实例,甚至在ApplicationSettings
从配置绑定并添加到 DI 容器之前执行。 - 可能与#1 相关,
cfg.AddMaps
方法调用直接使用 .NETSystem.Activator.CreateInstance
来实例化映射配置文件(例如MyMappingProfile
)并且不传递任何参数,即为什么会收到“未定义无参数构造函数”错误。此流程不使用传递给MapperConfiguration.CreateMapper
方法的serviceCtor
参数。 (AutoMapperMapperConfigurationExpression.AddMaps
调用AddMapsCore
,每个 v11.0.1 source 调用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
逻辑提取到映射配置文件 class 中的本地方法中,这是一个单独的自定义 Value Resolver (or Member Value Resolver) used with opt.MapFrom
, or a separate custom Value Converter 用于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 问题中,例如 This resolve operation has already ended. Autofac, Automapper & IMemberValueResolver. Dennis Doomen gives a good explanation of this Autofac design nuance in The curious case of a deadlock in Autofac。
So to summarize, use the temporary container for resolving dependencies needed at construction time, but use the global container to resolve any run-time dependencies.
另一种软件设计替代方案是根本不解析 AutoMapper 配置文件内的 DI 容器中的 ApplicationSettings
。相反,调用 IMapper.Map
的 class 可以通过构造函数注入注入 IOptions<ApplicationSettings>
,然后就地转换 EmpDto.EmpUrl
或添加单独的 属性,如 EmpDto.EmpFullUrl
将基本域前缀添加到 EmpDto.EmpPath
属性 通过 AutoMapper 从 Emp.EmpPath
逐字映射。