C# 使用Automapper和Autofac时为不可访问的实例指定构造函数
场景 假设我们有一个类C# 使用Automapper和Autofac时为不可访问的实例指定构造函数,c#,dependency-injection,architecture,automapper,autofac,C#,Dependency Injection,Architecture,Automapper,Autofac,场景 假设我们有一个类Target,它有以下两个构造函数(一个objec,或者两个Object和一个Enum) 然后我们有一个ClassA,它需要执行从某个对象到Target实例的映射操作。其中一个版本是以下版本,它依赖于现有的映射规则(对于automapper)和使用AutoFac的依赖项注入来提供执行映射的参数。这将使用上面两个构造函数中的最后一个(3个参数): 第一个挑战 对于ClassB,调用操作需要所有三个参数的值来Target,并通过DI为这些参数提供值 然而,对于ClassC,不仅
Target
,它有以下两个构造函数(一个objec,或者两个Object和一个Enum)
然后我们有一个ClassA
,它需要执行从某个对象到Target
实例的映射操作。其中一个版本是以下版本,它依赖于现有的映射规则(对于automapper)和使用AutoFac的依赖项注入来提供执行映射的参数。这将使用上面两个构造函数中的最后一个(3个参数):
第一个挑战
对于ClassB
,调用操作需要所有三个参数的值来Target
,并通过DI为这些参数提供值
然而,对于ClassC
,不仅不需要paramTwo
和paramTwoConfigEnum
;在这种情况下,它们不能通过DI提供。换句话说,我希望在本例中调用Target
中的另一个构造函数
尝试的解决方案
我意识到我可以在设置AutoFac规则时指定要使用的构造函数,并在特定情况下覆盖这些构造函数,因此我在我的ContainerBuilder中尝试了以下常规设置:
// This specifies that the constructor that takes a single param of type ParamOne
// should be used by default:
builder.RegisterType<Target>().AsSelf().UsingConstructor(typeof(ParamOne));
这实际上部分起作用:在ClassA
中的映射导致调用3参数构造函数,而在ClassC
中的映射导致调用1参数构造函数
剩余问题
现在问题仍然存在于ClassB
中,但是:我看不到任何方法来配置它,以便它调用目标的3参数构造函数,因为可以说,实例化和映射是在较低的级别上定义的
那么我的问题是:我有没有办法指定(从ClassB
或其他地方)当Target
从ClassB
实例化时,它应该使用特定的构造函数
或者,是否有更好的策略来解决此问题?如果您想从DI中解析目标
参数,您还必须将它们注册到容器中(您可能有此功能,只需重复检查):
builder.RegisterType().AsSelf()。使用构造函数(()=>newparamone());
builder.RegisterType().AsSelf()。使用构造函数(()=>newparamtow2());
builder.RegisterType().AsSelf()。使用构造函数(()=>paramtowenum.Default);
然后,您可以使用Lucian建议的ConstructionUsingServiceLocator()
,以及一个类型转换器,您可以通过DI向其中注入参数。映射配置:
CreateMap();
CreateMap()
.ConvertUsing();
CreateMap()
.ConstructionUsingServiceLocator();
ClassBToTargetTypeConverter
:
// This specifies that the constructor that takes a single param of type ParamOne
// should be used by default:
builder.RegisterType<Target>().AsSelf().UsingConstructor(typeof(ParamOne));
公共类ClassBToTargetTypeConverter:ITypeConverter
{
私有只读参数_ParamOne;
private readonly paramtow2 _paramtow2;
私有只读ParamTwoEnum _paramTwoConfigParamTwoEnum;
public ClassBToTargetTypeConverter(ParamOne ParamOne、ParamTwo ParamTwo、ParamTwoEnum paramTwoConfigParamTwoEnum)
{
_paramOne=paramOne;
_paramtoo=paramtoo;
_paramTwoConfigParamTwoEnum=paramTwoConfigParamTwoEnum;
}
公共目标转换(ClassB源、目标目标、ResolutionContext上下文)
{
返回新目标(_paramOne,_paramtow2,_paramtowconfigparamtowenum);
}
}
总结:
ClassA
到Target
通常使用源对象属性进行映射
ClassB
到Target
的映射使用类型转换器,而类型转换器又使用构造函数来构造Target
,构造函数包含从容器解析的三个参数
ClassC
到Target
直接使用DI映射,其中Target
被注册为仅使用一个参数的构造函数构造
旁注:使用Autofac,您可以自由地在使用类型转换器进行ClassC
或ClassB
和使用DI进行切换。但是如果要使用默认的.NET Core DI引擎,则必须为ClassC
到Target
映射使用类型转换器,因为DI被设计为贪婪的,并且选择具有最多参数的构造函数。这意味着,如果您让.NET Core DI自己构造Target
,并在服务集合中注册所有三个参数,那么它会选择三个参数的构造函数而不是只有一个参数的构造函数,因为它太贪婪了。我最终使用了一种不同的方法来解决这个问题。这或多或少解释了我所做的事情:
将第二个参数设为默认值为null,可以在所有情况下使用此构造函数。请注意,我完全省略了第三个参数;相反,我将依赖Automapper来填充它的属性(这正是我最初无法做到的,也是我尝试使用特定构造函数的原因;代码的下一部分将展示我如何在最后设置它)
现在,当设置从ClassB
到Target
的映射时,我指定它应该从通过context
传入的值中拾取ConfigEnumProp
(带有键的项“MyConfigEnum”
):
…并在从ClassB
映射时作为不同的值:
var result = _mapper.Map<List<Target>>(instanceOfClassB,
options => options.Items["MyConfigEnum"] = someOtherValue);
var result=\u mapper.Map(instanceOfClassB,
options=>options.Items[“MyConfigEnum”]=someOtherValue);
最后,如果不需要ParamTwo
和ConfigEnum
,则可以忽略它们-构造函数将正常工作,属性将保留
// This specifies that the constructor that takes a single param of type ParamOne
// should be used by default:
builder.RegisterType<Target>().AsSelf().UsingConstructor(typeof(ParamOne));
// Direct manipulation of the rules for mapping to Target, since I'm
// mapping directly to Target. As mentioned below, this does not appear
// to be possible when mapping to classes that contain Target (i.e.
// when Target is mapped implicitly).
result = _mapper.Map<List<Target>>(
someOtherObject,
options =>
options.ConstructServicesUsing(t => new Target(_p1, _p2, myEnum)));
public Target(ParamOne, ParamTwo = null) { ... }
public MyEnumType ConfigEnum {get; set;}
// Map to ConfigEnum in Target NOT from the source, but from a value
// passed in via context by the caller:
CreateMap<ClassB, SectionsDTO>()
.ForMember(dest => dest.ConfigEnum,
opt => opt.MapFrom((src, dest, destMember, context) =>
context.Items["MyConfigEnum"]))
var result = _mapper.Map<List<Target>>(instanceOfClassA,
options => options.Items["MyConfigEnum"] = valueWhenMappingFromA);
var result = _mapper.Map<List<Target>>(instanceOfClassB,
options => options.Items["MyConfigEnum"] = someOtherValue);