1

所以,我正在尝试解决一个我确定其他人已经遇到过的问题。基本上,我希望调用我的 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) 

Looking through some online articles, it sounds like I need to use some kind of Unity interceptor, but it's almost like I'm writing my own IoC container at this point with some kind of testing architecture built in.

Hopefully someone has a good idea on how to do this before I go down to pain of finding the constructor and then resolving each type recursively.

EDIT: So It actually hasnt turned out to be that horrible to create my own injection by inspecting the constructor parameters of each dependency recursively, but I think the boss people might get a bit perturbed if I throw out Unity for my own custom solution.

4

4 回答 4

1

I would try to swap out the Unity container for the duration of the request. Detect that it's your "V2" condition then make a new container but with the different set of types registered. Use that in the scope of that request. It wouldn't be a good idea to make a new container on every request in production but it should be a fine idea for testing.

于 2011-10-04T18:29:24.957 回答
1

Assuming you can write an ABEmailSenderFactory with a Create method...

In Autofac it would be as easy as

builder.RegisterType<ABEmailSenderFactory>().AsSelf();

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

I don't know much about Unity, but it looks like it may not be as easy.

于 2011-10-04T19:46:53.140 回答
0

I think I'm just going to go down the road of making my custom solution. In effect, I was having to hack the current IoC I'm using to get the effect I wanted, which isnt much better than just making my own solution. I'm not even using the IoC for most of the functionality other than the simple resolution of dependencies, so I'm not sure I'll be missing using it. I ended up with the following method:

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

This way, I can control the exposure of the AB class instances via the database records.

于 2011-10-04T19:53:57.973 回答
0

There are a couple of options you can use here. I'm assuming Unity 2.0 here, this was harder in earlier versions.

You could pre-calculate the various permutations ahead of time and use resolver overrides in the resolve call:

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

This will replace the resolved object for SampleType wherever it appears in the tree, but it does require an instance directly.

You could use named registrations and do the NxM set of registrations for each combination of factors you're A/B testing against. Although that gets really messy after about 4 changes.

You could use an injectionfactory for things you're testing against - the factory could figure out which one to use:

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

This will do a runtime switch based on whatever the figureOutWhichType method does.

A third option would be a container extension that adds a type-mapping strategy to do the logic inside the resolve chain.

Of these options, I'd probably start with the factory approach, and move to the custom extension if things get way out of hand.

于 2011-10-05T21:56:02.873 回答