Ninject binding with WhenInjectedInto extension method
Asked Answered
A

1

6

I feel I am missing something obvious. I've read several related questions on here and I've read the updated contextual bindings page on Ninject's wiki but alas it still doesn't work.

I am trying to retrofit a legacy application that used a factory pattern to use Ninject.

I have 1 interface (IInterface) implemented by 2 classes (ClassB and ClassC). IInterface has a load method. In ClassB's load method it instantiates ClassC and then executes it's load method.

Basically the program flow is ClassA creates ClassB and executes the load method. In the load method ClassB creates ClassC that does some work.

My bindings are setup as so...

Bind<IInterface>().To<ClassC>().WhenInjectedInto<ClassB>();
Bind<IInterface>().To<ClassB>().WhenInjectedInto<ClassA>();

When this runs it fails in ClassB's load method with this error...

Error activating IInterface No matching bindings are available, and the type is not self-bindable.

If I try the following...

Bind<IInterface>().To<ClassC>().WhenInjectedInto<ClassB>();
Bind<IInterface>().To<ClassB>();

It does an endless loop and never creates ClassC.

EDIT I have simplified this into a unit test that passes but doesn't give me the results I want...

[TestClass]
public class NinjectTestFixture
{
    private interface IDoSomething
    {
        void SaySomething();
    }

    private class ClassA : IDoSomething
    {
        public void SaySomething()
        {
            Console.WriteLine("Hello from Class A");
        }
    }

    private class ClassB : IDoSomething
    {
        private IKernel _Kernel;

        public ClassB(IKernel kernel)
        {
            _Kernel = kernel;
        }

        public void SaySomething()
        {
            Console.WriteLine("Hello from Class B");

            var x = _Kernel.Get<IDoSomething>();

            x.SaySomething();
        }
    }

    private class ClassC
    {
        private IKernel _Kernel;

        public ClassC(IKernel kernel)
        {
            _Kernel = kernel;
        }

        public void SaySomething()
         {
             Console.WriteLine("Hello from Class C");

             var x = _Kernel.Get<IDoSomething>();

             x.SaySomething();
         }
    }

    [TestMethod]
    public void TestMethod1()
    {
        var kernel = new StandardKernel();

        kernel.Bind<IDoSomething>().To<ClassA>();
        kernel.Bind<IDoSomething>().To<ClassB>().WhenInjectedInto<ClassC>();
        kernel.Bind<ClassC>().ToSelf();

        var x = kernel.Get<ClassC>();

        x.SaySomething();
    }

The output is: Hello from Class C Hello from Class A

But I want: Hello from Class C Hello from Class B Hello from Class A

Thanks

Amadeus answered 17/9, 2011 at 22:25 Comment(0)
B
6

You are not injecting into ClassC. You are passing in the kernel and resolving IDoSomething directly from it. There is a big difference.

Do not pass the kernel as a parameter--doing so is not Dependency Injection, it is Service Location (good article on the difference: Service Locator is an Anti-Pattern).

Change ClassC to be:

private class ClassC     
{         
    private IDoSomething _doSomething;          
    public ClassC(IDoSomething doSomething)
    {             
        _doSomething = doSomething;         
    }          

    public void SaySomething()          
    {              
        Console.WriteLine("Hello from Class C");               
        //var x = _Kernel.Get<IDoSomething>();               
        _doSomething.SaySomething();          
    }     
} 

You should also make the same changes to ClassA and ClassB (pass the type/interface you want resolved, not the kernel).

Bung answered 18/9, 2011 at 18:39 Comment(3)
I understand what you're saying but in the method SaySomething I need to instantiate an object to do some work. In the same class there are other methods that instantiate methods to do more work. If I did what you're suggesting I'd have about 20 parameters in my constructor. The code was originally written over 6 years ago so I'm trying to retrofit it with DI gradually. I've racked my brain on how to do this and passing the kernel was the easiest solution that made the most sense. I looked ay service location and that didn't make sense given the current code base.Amadeus
You answer is correct. When I made the changes you proposed I got the results I expected.Amadeus
I get what you mean--I too have refactored systems where I had to use some odd/ugly interim steps to introduce DI.Bung

© 2022 - 2024 — McMap. All rights reserved.