首页 > 解决方案 > 使用 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);

接下来,我们还有另外两个类ClassBClassC。这两者都需要执行类似的映射操作,但这里生成的对象是包含;实例的类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,它应该使用一些特定的构造函数?

或者,是否有更好的策略来解决这个问题?

标签: c#dependency-injectionarchitectureautomapperautofac

解决方案


如果你想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);
    }
}
概括:
  • ClassAtoTarget使用源对象属性正常映射
  • ClassBtoTarget使用类型转换器映射,该类型转换器又Target使用构造函数构造,其中三个参数从容器中解析
  • ClassCtoTarget直接使用 DI 映射,其中Target注册为使用只有一个参数的构造函数构造

旁注:使用 Autofac,您可以自由地在使用类型转换器 forClassC或 forClassB和使用 DI 之间切换。但!如果您要使用默认的 .NET Core DI 引擎,则必须使用类型转换器进行ClassC映射Target,因为 DI 被设计为贪婪并选择具有最多可填充参数的构造函数。这意味着,如果您让 .NET Core DI 自己构造Target,并在服务集合中注册所有三个参数,那么它将选择具有三个参数的构造函数而不是只有一个参数的构造函数,因为它是贪婪的。


推荐阅读