1

So, I'm trying to solve a problem that I'm sure someone else has already encountered. Basically, I want the call to my IoC container to recursively resolve the dependencies, but also potentially execute some custom code to alter the outcome based on a set of predefined criteria. That's vague, so let me give an example:

Say I have a controller like so:

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

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

I've also got some test versions of this controller (say I refactored it and I want to make sure it doesnt break horribly in prod by AB testing it's exposure):

 public class SampleController_V2 : SampleController
 {
      protected SampleType _sampleType = null;
      protected AnotherType _anotherType = null;

      public SampleController_V2(SampleType sampleType, AnotherType anotherType)
      {
          _sampleType = sampleType;
          _anotherType = anotherType;
      }
 }

I've extended the DefaultControllerFactory to use Unity when creating the controllers. This is all working fine. Now, what I want to do, it provide the ability to AB test any particular type in the hierarchy if things to resolve. This works well for the top level, but not for child elements as it recurses through the object graph.

Right now, it will choose the appropriate controller to resolve and give it its dependencies. However, I cant seem to intercept the individual calls to dependencies to also AB test those. I can define a test through database configuration, and then have the IOC container resolve it based on the criteria. Example:

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

This all works for the top level item. However, the call to _unityContainer.Resolve(type) doesnt seem to be a recursive call; I'd like to be able to inject that code into any point when it tries to resolve a 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.

Tejs
  • 40,736
  • 10
  • 68
  • 86
  • I could be misunderstanding. You want to resolve different object graphs depending on the test data being input? I'm not sure why you want to do this - when I test with the container, I try to keep the object graph as close to production as possible. – default.kramer Oct 04 '11 at 18:44
  • It's to provide AB testing for the specific objects. For example: Say I have an EmailSender class. It does X. However, I want to test a rewritten EmailSender class. I have the IOC container resolve an EmailSender_V2 class in set of situations in it's place that will do Y. Say it may be performance intensive; I want to slowly introduce it to see if it is negatively impacting the site. I can then ramp to 100% and move that code to the base version in the future without modifying the control version. Most users then get the same experience; some users get an enhanced experience while testing. – Tejs Oct 04 '11 at 18:50

4 Answers4

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.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113
  • So you're saying make a temporary container that does `Register()` for that specific request? – Tejs Oct 04 '11 at 18:37
  • The only problem is then that potentially every request would have a temporary unity container. Isnt that expensive to construct? – Tejs Oct 04 '11 at 18:56
  • 2
    Sorry, I misunderstood the circumstances of the question. I thought you needed the alternate types for testing only. If you've got logic that decides which type to return I think it would be a good idea to make an explicit factory class. That way you can test the factory itself as well. – RandomEngy Oct 04 '11 at 19:00
  • So you would dependency inject the factories and leave the instance instantiation to each factory? That seems like I would baloon the classes I need to create if for each type, I also need to make a Factory... – Tejs Oct 04 '11 at 19:04
  • Well only for the types that require special logic before deciding what class to use. You'd want that to be a testable part of your application anyway. – RandomEngy Oct 04 '11 at 19:19
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.

Community
  • 1
  • 1
default.kramer
  • 5,943
  • 2
  • 32
  • 50
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.

Tejs
  • 40,736
  • 10
  • 68
  • 86
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.

Chris Tavares
  • 29,165
  • 4
  • 46
  • 63