Unity IOC递归-拦截器的使用

本文关键字:IOC 递归 Unity | 更新日期: 2023-09-27 18:10:22

所以,我正试图解决一个问题,我相信别人已经遇到过。基本上,我希望对IoC容器的调用递归地解析依赖项,但也可能执行一些自定义代码来根据一组预定义的标准更改结果。这是模糊的,所以让我给一个例子:

假设我有一个这样的控制器:

 public class SampleController : Controller
 {
      protected SampleType _sampleType = null;
      public SampleController(SampleType sampleType)
      {
          _sampleType = sampleType;
      }
 }

我也有这个控制器的一些测试版本(说我重构了它,我想确保它不会打破可怕的AB测试它的暴露):

 public class SampleController_V2 : SampleController
 {
      protected SampleType _sampleType = null;
      protected AnotherType _anotherType = null;
      public SampleController_V2(SampleType sampleType, AnotherType anotherType)
      {
          _sampleType = sampleType;
          _anotherType = anotherType;
      }
 }

我已经扩展了DefaultControllerFactory在创建控制器时使用Unity。这一切都很好。现在,我想做的是,它提供了在层次结构中对任何特定类型进行AB测试的能力。这对于顶层很有效,但对于子元素就不行了,因为它在对象图中递归。

现在,它将选择合适的控制器来解析并给它它的依赖项。然而,我似乎无法拦截对依赖项的单独调用,也无法对这些调用进行AB测试。我可以通过数据库配置定义测试,然后让IOC容器根据标准解析测试。例子:

SessionIds that start with the letter 'a': SampleController_V2
Everyone Else                            : SampleController
UserIds ending in 9                      : SampleType_V2
Everyone Else                            : SampleType

这些都适用于顶级项。然而,对_unityContainer.Resolve(type)的调用似乎不是一个递归调用;我希望能够在它试图解析类型时将该代码注入任何点:

-> Attempt to Resolve SampleController
    -> Test Code Tells Us to use _V2 if possible. 
    -> Attempt to Resolve SampleType
       -> Test Code tells us to use the _V1 if possible.
    -> Resolves SampleType_V1
    -> Attempt to Resolve AnotherType
       -> No Test Defined, Use the Default
    -> Resolves AnotherType
-> Resolves SampleController_V2 (passing SampleType_V1 as the dependency and then AnotherType as the other dependency) 

通过一些在线文章,听起来我需要使用某种Unity拦截器,但这几乎就像我在编写自己的IoC容器,其中内置了某种测试架构。

希望有人有一个好主意,如何做到这一点之前,我去寻找构造函数,然后递归地解析每个类型的痛苦。

编辑:所以它实际上并没有被证明是可怕的,通过检查每个依赖的构造函数参数递归地创建我自己的注入,但我认为老板的人可能会有点不安,如果我抛弃Unity为我自己的自定义解决方案。

Unity IOC递归-拦截器的使用

我会尝试在请求期间更换Unity容器。检测到这是您的"V2"条件,然后创建一个新容器,但要注册不同的类型集。在请求的范围内使用它。在生产环境中为每个请求都创建一个新的容器不是一个好主意,但是在测试中应该是一个好主意。

假设您可以用Create方法编写ABEmailSenderFactory

在autoface中,它就像

一样简单
builder.RegisterType<ABEmailSenderFactory>().AsSelf();
builder.Register(c => c.Resolve<ABEmailSenderFactory>().Create())
    .As<EmailSender>();

我不太了解Unity,但看起来它可能不那么容易。

我想我要做我的自定义解决方案。实际上,我必须破解我正在使用的当前IoC来获得我想要的效果,这并不比我自己制作解决方案好多少。除了依赖项的简单解析之外,我甚至没有将IoC用于大多数功能,所以我不确定我会错过使用它。我最终使用了以下方法:

public virtual object Resolve(Type requestedType, Session session = null, RequestContext requestContext = null)
{
    ConstructorInfo constructor = requestedType.GetConstructors().First();
    object[] dependencies = null;
    Type resultType = requestedType;
    if (session == null || requestContext == null)
    {
        dependencies = constructor.GetParameters().Select(parameter => Resolve(parameter.ParameterType)).ToArray();
    }
    else
    {
        InitializeTestingArchitecture();
        var testVersion = _testingProvider.GetTestVersionFor(requestedType, requestContext, session);
        if(testVersion == null)
            return Resolve(requestedType);
        resultType = _testTypeLoader.LoadTypeForTestVersion(requestedType, testVersion);
        constructor = resultType.GetConstructors().First();
        dependencies = constructor.GetParameters().Select(parameter => Resolve(parameter.ParameterType, session, requestContext)).ToArray();
    }
    return Activator.CreateInstance(resultType, dependencies);
}
这样,我就可以通过数据库记录控制AB类实例的公开。

这里有几个选项可以使用。我假设这里是Unity 2.0,这在早期版本中比较困难。

您可以提前预计算各种排列,并在resolve调用中使用解析器覆盖:

container.Resolve(figureOutControllerType(),
    new DependencyOverride<SampleType>(figureOutSampleType()));

这将替换SampleType在树中出现的任何地方的已解析对象,但它确实直接需要一个实例。

您可以使用命名注册,并为您进行A/B测试的每个因素组合执行NxM注册集。虽然修改了4次之后会变得很乱。

你可以为你要测试的东西使用注入工厂——工厂可以找出使用哪一个:

container.RegisterType<SimpleType>(
    new InjectionFactory((c) => container.Resolve(figureOutWhichType()));

这将根据figureouthtmltype方法的操作进行运行时切换。

第三种选择是一个容器扩展,它添加了一个类型映射策略来执行解析链中的逻辑。

在这些选项中,我可能会从工厂方法开始,如果事情无法控制,则转向自定义扩展。