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);