Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject scoping - use same instance across entire graph being constructed

Let's say I have the following classes that I want to construct using Ninject, with the arrows showing dependencies.

A > B > D
A > C > D

I want to configure Ninject such that A is transient scoped, i.e. every time you ask Ninject for an A, you get a new one. I also want B and C to be transient, you get a new one of those every time you ask for an A. But I want the D to be reused across both B and C. So every time I request an A, I want Ninject to construct one of each object, not two Ds. But I don't want Ds to be reused across different As.

What is the best way to set this up using Ninject?

Update:
After some more research, it seems like Unity has a PerResolveLifetimeManager which does what I'm looking for. Is there a Ninject equivalent?

like image 911
RationalGeek Avatar asked Dec 26 '12 17:12

RationalGeek


3 Answers

Ninject supports four built in object scopes out of the box: Transient, Singleton, Thread, Request.

So there isn't any PerResolveLifetimeManager like scope but you can implement it easily with registering a custom scope with the InScope method.

As it turned out there is an existing Ninject extension: ninject.extensions.namedscope which provides the InCallScope method which is what you are looking for.

However if you want to do it yourself you can do with a custom InScope delegate. Where you can use the main IRequest object for the type A to use it as the scope object:

var kernel = new StandardKernel();
kernel.Bind<A>().ToSelf().InTransientScope();
kernel.Bind<B>().ToSelf().InTransientScope();
kernel.Bind<C>().ToSelf().InTransientScope();
kernel.Bind<D>().ToSelf().InScope(
    c =>
        {
            //use the Request for A as the scope object                         
            var requestForA = c.Request;
            while (requestForA != null && requestForA.Service != typeof (A))
            {
                requestForA = requestForA.ParentRequest;
            }
            return requestForA;
        });

var a1 = kernel.Get<A>();    
Assert.AreSame(a1.b.d, a1.c.d);

var a2 = kernel.Get<A>();    
Assert.AreSame(a2.b.d, a2.c.d);

Assert.AreNotSame(a1.c.d, a2.c.d);

Where the sample classes are:

public class A
{
    public readonly B b;
    public readonly C c;
    public A(B b, C c) { this.b = b; this.c = c; }
}

public class B
{
    public readonly D d;
    public B(D d) { this.d = d; }
}

public class C
{
    public readonly D d;  
    public C(D d) { this.d = d; }
}

public class D { }
like image 134
nemesv Avatar answered Oct 17 '22 04:10

nemesv


I found the solution to my specific problem, which is InCallScope that is provided by the ninject.extensions.namedscope extension. This behaves identically to the Unity PerResolveLifetimeManager concept.

like image 21
RationalGeek Avatar answered Oct 17 '22 02:10

RationalGeek


One alternative is to construct the dependencies yourself.

var kernel = new StandardKernel();
kernel.Bind<A>().ToMethod(ctx =>
{
    var d = new D();
    var c = new C(d);
    var b = new B(d);
    var a = new A(b, c);
    return a;
});

This may not be the preferred method, but it will always construct a new instance of A using a new instance of B, C, and D (but reusing the same instance of D for B and C). You can add a call to InTransientScope(), but that is not required as it is the default scope.

like image 1
Kevin Babcock Avatar answered Oct 17 '22 03:10

Kevin Babcock