RationalGeek
RationalGeek

Reputation: 9609

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?

Upvotes: 15

Views: 3756

Answers (3)

nemesv
nemesv

Reputation: 139798

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 { }

Upvotes: 15

RationalGeek
RationalGeek

Reputation: 9609

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.

Upvotes: 6

Kevin Babcock
Kevin Babcock

Reputation: 10247

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.

Upvotes: 1

Related Questions