c# - 使用 Automapper 和 Autofac 时为不可访问的实例指定构造函数
问题描述
设想
假设我们有一个Target
具有以下两个构造函数的类(一个对象,或两个对象和一个枚举)。
public Target(paramOne) { ... }
public Target(paramOne, paramTwo, paramTwoConfigEnum) { ... }
然后我们有一个ClassA
需要执行从某个对象到一个实例的映射操作Target
。其中一个版本如下,它依赖于存在映射规则(用于自动映射器),以及使用 AutoFac 提供参数来执行映射的依赖注入。这使用了上面两个构造函数中的最后一个(3 个参数):
// Behind the scenes this performs a call like: new Target(p1, p2, myEnum)
// with p1, p2 and
var result = _mapper.Map<List<Target>>(someOtherObject);
接下来,我们还有另外两个类ClassB
和ClassC
。这两者都需要执行类似的映射操作,但这里生成的对象是包含;实例的类Target
。换句话说,这里也有一个幕后的隐式someOtherObject
映射Target
:
// Behind the scenes this performs calls conceptually similar to the following
// (NB: The second line here will call new Target() with 3 params, as above):
// var x = new ClassContainingTarget(..)
// x.instOfTarget = _mapper.Map<List<Target>>(someOtherObject);
var result = _mapper.Map<ClassContainingTarget>(anotherSourceObject);
第一个挑战
对于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));
使用此设置,Map()
上面的所有调用都将导致使用第二个(单参数)构造函数Target
,包括在 的情况下ClassC
,这正是我想要的。
对于ClassA
那时的映射,我可以通过将上面显示的操作替换为以下内容来覆盖此逻辑Map()
:
// 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)));
这实际上部分起作用:映射 inClassA
会导致调用 3-param 构造函数,而 inClassC
会导致调用 1-param 构造函数。
剩下的问题
但是现在问题仍然ClassB
存在:我看不到任何配置它的方法,以便它调用 3-param 构造函数Target
,因为可以这么说,实例化和映射是在较低级别定义的。
所以我的问题是:我有什么方法可以指定(从ClassB
或其他地方)当Target
从 实例化时ClassB
,它应该使用一些特定的构造函数?
或者,是否有更好的策略来解决这个问题?
解决方案
如果你想Target
从 DI 解析参数,你还必须在容器中注册它们(你可能有这个,只是仔细检查):
builder.RegisterType<ParamOne>().AsSelf().UsingConstructor(() => new ParamOne());
builder.RegisterType<ParamTwo>().AsSelf().UsingConstructor(() => new ParamTwo());
builder.RegisterType<ParamTwoEnum>().AsSelf().UsingConstructor(() => ParamTwoEnum.Default);
然后您可以ConstructUsingServiceLocator()
按照 Lucian 的建议使用类型转换器,您可以通过 DI 向其中注入参数。映射配置:
CreateMap<ClassA, Target>();
CreateMap<ClassB, Target>()
.ConvertUsing<ClassBToTargetTypeConverter>();
CreateMap<ClassC, Target>()
.ConstructUsingServiceLocator();
ClassBToTargetTypeConverter
: _
public class ClassBToTargetTypeConverter : ITypeConverter<ClassB, Target>
{
private readonly ParamOne _paramOne;
private readonly ParamTwo _paramTwo;
private readonly ParamTwoEnum _paramTwoConfigParamTwoEnum;
public ClassBToTargetTypeConverter(ParamOne paramOne, ParamTwo paramTwo, ParamTwoEnum paramTwoConfigParamTwoEnum)
{
_paramOne = paramOne;
_paramTwo = paramTwo;
_paramTwoConfigParamTwoEnum = paramTwoConfigParamTwoEnum;
}
public Target Convert(ClassB source, Target destination, ResolutionContext context)
{
return new Target(_paramOne, _paramTwo, _paramTwoConfigParamTwoEnum);
}
}
概括:
ClassA
toTarget
使用源对象属性正常映射ClassB
toTarget
使用类型转换器映射,该类型转换器又Target
使用构造函数构造,其中三个参数从容器中解析ClassC
toTarget
直接使用 DI 映射,其中Target
注册为使用只有一个参数的构造函数构造
旁注:使用 Autofac,您可以自由地在使用类型转换器 forClassC
或 forClassB
和使用 DI 之间切换。但!如果您要使用默认的 .NET Core DI 引擎,则必须使用类型转换器进行ClassC
映射Target
,因为 DI 被设计为贪婪并选择具有最多可填充参数的构造函数。这意味着,如果您让 .NET Core DI 自己构造Target
,并在服务集合中注册所有三个参数,那么它将选择具有三个参数的构造函数而不是只有一个参数的构造函数,因为它是贪婪的。
推荐阅读
- flutter - CustomPainter 和 CustomClipper 为同一路径产生不同的输出
- google-apps-script - 仅充当 Google 客户端的可发布插件的 OAuth 要求
- jquery - 如何比较jquery中的以下值?
- javascript - Express.js - 允许无限的子路由,但至少有一个
- r - R:lmer()错误消息:未使用的参数(family =“binomial”)
- node.js - 在生产环境模式下运行 node.js
- flutter - 如何在颤动中处理 ListView 内的图像宽度?
- java - 如何配置最少读取的消息 - Spring Kafka
- ios - 如何在折线图中仅显示 24 小时列?
- python - 无法使用 pycharm 调试 Dev Appserver,但能够正常运行