views:

42

answers:

1

I am building a framework that I don't want to couple to a particular IOC container so have created a layer on top of Ninject / structuremap etc.

I have a binding class that accepts a Func to allow binding to a method.

For example

public class Binding
{
 public Type Source { get; set; }
 public Func<object> Method {get; set; }
 public Scope { get; set; }
}

If I have a binding like...

var binding = new Binding() { 
Source = typeof(IRepository),
Method = () => new Repository(new LinqToSqlRepository(connectionString)),
Scope = Scope.HttpRequest
};

The framework wrapping Ninject creates Ninject bindings for my generic binding like this

Module :NinjectModule
{
 IList<Binding> _Bindings;

 public Module(IList<Binding> bindings)
 {
    _Bindings = bindings;
 }

 public override void Load() { 
    foreach (var binding in _Bindings) {
      switch(binding.Scope) {
        case IocScope.HttpRequest:
          Bind(binding.Source).ToMethod(c => binding.Method()).InRequestScope();
          break;
        // ... omitted for brevity
      }
    }
  }
}

This works fine when there is only one binding being bound to a method. When there are multiple bindings being bound within the same module to methods however the incorrect type is returned. From debugging, it looks as if the last binding is always used.

Thus the problem with an example;

var binding1 = new Binding() { 
 Source = typeof(IRepository),
 Method = () => new Repository(new LinqToSqlRepository(connectionString)),
 Scope = Scope.HttpRequest
};

var binding2 = new Binding() { 
 Source = typeof(ICalendar),
 Method = () => new MvcCalendar( ..... )
 Scope = Scope.HttpRequest
};

At runtime when Ninject is requested to new up an MVC Controller which takes in an IRepository and an ICalendar, I receive a type conversion error saying that a MvcCalendar cannot be converted to an IRepository. I have discovered that for some reason the last binding is always being returned for the first requested type.

This is a highly simplified version of what is really going on to try and highlight the actual issue, the wrong method being bound to a requested type when there are multiple method bindings. I hope this still explains the issue though.

This appears to be related to some sort of closure scoping issue. I also wonder whether Ninject is getting is getting confused by the Func instead of Func usage.

Unit Test Example

Here is a test module I load into my custom IOC container. This does not depend on any particular IOC framework. When I instantiate a NinjectIocContainer to handle the DI, the internal binding of this in Ninject occurs as example further up (see NinjectModule)

public class MultipleMethodBoundTypesModule : IocModule
{
    public override void Load()
    {
        Bind<IPerson>().To(() => new Person()).In(IocScope.Transient);
        Bind<IRobot>().To(() => new Robot(new Person())).In(IocScope.Transient);
    }
}

Here is a simple test that tries to retrieve each of the types.

    [Test]
    public void   Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()
    {
        // arrange
        var container = Get_Container_With_Module(new MultipleMethodBoundTypesModule());

        // act
        var person = container.Get<IPerson>();
        var robot = container.Get<IRobot>();

        // assert
        Assert.IsNotNull(person);
        Assert.IsNotNull(robot);
    }

As explained eariler, this throws a type conversion where the last closure (for the robot) is being bound to a person.

TestCase 'Ioc.Test.NinjectContainerTest.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module' failed: System.InvalidCastException : Unable to cast object of type 'Ioc.Test.Robot' to type 'Ioc.Test.IPerson'. at System.Linq.Enumerable.d__b11.MoveNext() at System.Linq.Enumerable.Single[TSource](IEnumerable1 source) at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters) NinjectIocContainer.cs(40,0): at Ioc.Ninject.NinjectIocContainer.GetTInstance IocTestBase.cs(149,0): at Ioc.Test.IocTestBase.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()

A: 

In the snippet:

      Bind(binding.Source).ToMethod(binding.Method()).InRequestScope();

You're dereferencing the Method bit. You want to be doing that as either binding.Method or ()=>binding.Method() (the former may not unambiguously be inferrable based on the C# type inference rules).

You mentioned this is heavily stripped down from your real code. As a result, this may not be the actual issue. I'd still be betting on some form of closure confusion though (see the section Comparing capture strategies: complexity vs power in this CSID excerpt for a nice walkthrough).

You also probably meant to use .InScope(binding.Scope) rather than .InRequestScope() too,.

Ruben Bartelink
I didn't have the code infront of me and was typing off the top of my head and made a typo in the .ToMethod(binding.Method()). As this is a Ninject binding, it should actually include a context; (c => binding.ToMethod()). I have updated the original question to reflect this. Once again, the scoping on this is correct because this is inside a NinjectModule. I did notice a similar issue on the linked 'closure confusion' article where the last value of the forloop was repeated. I'm not entirely sure how to apply that to my case yet however.
Joshua Hayes
It appears for primitives, the value is copied but how does this apply when returning objects which I assume would be returned by ref.
Joshua Hayes
@Joshua Hayes: Unfortunately without your codebase and VS in front of me, I dont feel I can guess in any more useful a direction. Still dont understand why you're storing a scope and then using .InRequestScope(). One final point, I didnt make - I'm wondering why you feel you need to abstract the container without going the CSL route. I personally generally use ctor injection and have code container neutral - having this layer just makes a mess - but I'm sure your situation is different to the types of codebases I happen to be working on at the present time...
Ruben Bartelink
@Ruben - I do use ctor injection as you normally would. But somewhere you are bootstrapping the environment with StructureMap or Ninject and loading up some modules that define what types are injected into your constructors. This ties your applicaiton to a particular IOC/DI framework. For most applications, this is fine. However when you are building a framework which somebody else is going to extend, its nice to let them choose their IOC/DI framework and not impose your own on them. This means they do not have to re-write all the boostrapping code whenever they change their DI Container.
Joshua Hayes
@Ruben - Regarding the binding issue you picked up. As stated in the question, this code is highly contrived and simplified in order to demonstrate the problem. In the actual code, .InRequestScope() is not hard coded and the correct scope is chosen from binding.Scope. To do that here though would require switching on the enumeration binding.Scope and implementing the correct Ninject scope which was not the purpose of this question.
Joshua Hayes
@Joshua Hayes: Sounds like you're well on top of things so; Good luck resolving your actual problem! Random link: http://brendan.enrick.com/post/your-ioc-container-is-showing.aspx Is CSL applicable for you ? http://www.codeproject.com/Articles/51640/Common-Service-Locator-Separate-hard-reference-of-.aspx http://stackoverflow.com/questions/735712/when-would-you-use-the-common-service-locator (None of these may have any relevance, they're just stuff I walked over a year ago when I last assessed things...)
Ruben Bartelink