【问题标题】:Unity IOC Recursion - A use for interceptors?Unity IOC 递归 - 拦截器的用途?
【发布时间】:2023-03-28 11:18:01
【问题描述】:

所以,我正在尝试解决一个我确信其他人已经遇到过的问题。基本上,我希望调用我的 IoC 容器以递归方式解决依赖关系,但也可能执行一些自定义代码以根据一组预定义的标准更改结果。这很模糊,所以让我举个例子:

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

 public class SampleController : Controller
 {
      protected SampleType _sampleType = null;

      public SampleController(SampleType sampleType)
      {
          _sampleType = sampleType;
      }
 }

我也有这个控制器的一些测试版本(比如说我重构了它,我想通过 AB 测试它的曝光确保它不会在 prod 中严重损坏):

 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,老板们可能会有点不安解决方案。

【问题讨论】:

  • 我可能误会了。您想根据输入的测试数据解析不同的对象图吗?我不确定您为什么要这样做 - 当我使用容器进行测试时,我会尽量让对象图尽可能接近生产环境。
  • 为特定对象提供AB测试。例如:假设我有一个 EmailSender 类。它确实是 X。但是,我想测试一个重写的 EmailSender 类。我让 IOC 容器在它的位置解析一个 EmailSender_V2 类,它会做 Y。说它可能是性能密集型的;我想慢慢介绍它,看看它是否对网站产生了负面影响。然后,我可以提高到 100%,并在将来将该代码移至基本版本,而无需修改控制版本。然后大多数用户获得相同的体验;一些用户在测试时获得了增强的体验。

标签: c# asp.net-mvc recursion unity-container ab-testing


【解决方案1】:

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

【讨论】:

  • 所以你是说创建一个临时容器来处理Register<SampleType, SampleType_V2>() 的特定请求?
  • 唯一的问题是每个请求都可能有一个临时的统一容器。建造起来不是很贵吗?
  • 对不起,我误解了问题的情况。我以为您只需要备用类型进行测试。如果您有决定返回哪种类型的逻辑,我认为创建一个明确的工厂类是个好主意。这样你也可以测试工厂本身。
  • 所以你会依赖注入工厂并将实例实例化留给每个工厂?如果对于每种类型,我还需要创建一个 Factory...
  • 仅适用于在决定使用什么类之前需要特殊逻辑的类型。无论如何,您都希望它成为您应用程序的可测试部分。
【解决方案2】:

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

在 Autofac 中它就像

一样简单
builder.RegisterType<ABEmailSenderFactory>().AsSelf();

builder.Register(c => c.Resolve<ABEmailSenderFactory>().Create())
    .As<EmailSender>();

我对 Unity 了解不多,但是it looks like it may not be as easy

【讨论】:

    【解决方案3】:

    我想我将继续制作我的自定义解决方案。实际上,我不得不破解我正在使用的当前 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 类实例的暴露。

    【讨论】:

      【解决方案4】:

      您可以在此处使用几个选项。我假设这里是 Unity 2.0,这在早期版本中更难。

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

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

      这将替换树中出现的 SampleType 的已解析对象,但它确实需要直接实例。

      您可以使用命名注册,并针对您要进行 A/B 测试的每种因素组合进行 NxM 注册集。尽管在大约 4 次更改后变得非常混乱。

      您可以对您正在测试的东西使用注入工厂 - 工厂可以确定使用哪一个:

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

      这将根据 figureOutWhichType 方法所做的任何事情进行运行时切换。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-17
        • 1970-01-01
        • 1970-01-01
        • 2018-06-16
        • 1970-01-01
        相关资源
        最近更新 更多