dependency-injectionninjectninject-extensions

Ninject: How to access root object of NamedScope from factory


In my application I am using Ninject and the NamedScopeExtension. One of the objects deeper in the object graph needs access to the root object that defined the named scope. It seems to me that DefinesNamedScope() does not also imply InNamedScope() and instead a new root object is created when I request the root.

Example:

using System;
using Ninject;
using Ninject.Extensions.NamedScope;
using Ninject.Syntax;

namespace NInjectNamedScope
{
    public interface IScopeRoot
    {
        Guid Guid { get; }
        void DoSomething();
    }

    public interface IFactory
    {
        Guid Guid { get; }
        IOther CreateOther();
    }

    public interface IOther
    {
        void SayHello();
    }

    internal class ScopeRoot : IScopeRoot
    {
        private readonly IFactory m_factory;
        private readonly IResolutionRoot m_kernel;
        public Guid Guid { get; private set; }

        public ScopeRoot(IFactory factory, IResolutionRoot kernel)
        {
            m_factory = factory;
            m_kernel = kernel;
            Guid = Guid.NewGuid();
        }

        public void DoSomething()
        {
            Console.WriteLine("ScopeRoot.DoSomething(): Entering");
            Console.WriteLine("ScopeRoot GUID: {0}", Guid);
            Console.WriteLine("IFactory  GUID: {0}", m_factory.Guid);
            var other = m_factory.CreateOther();
            Console.WriteLine("ScopeRoot.DoSomething(): Other created");
            other.SayHello();
            Console.WriteLine("ScopeRoot.DoSomething(): Exiting");
        }
    }

    internal class Factory : IFactory
    {
        private IResolutionRoot m_kernel;

        public Guid Guid { get; private set; }

        public Factory(IResolutionRoot kernel)
        {
            m_kernel = kernel;
            Guid = Guid.NewGuid();
        }
        public IOther CreateOther()
        {
            return m_kernel.Get<IOther>();
        }
    }

    internal class Other : IOther
    {
        private readonly IScopeRoot m_root;
        private readonly IFactory m_factory;

        public Other(IScopeRoot root, IFactory factory)
        {
            m_root = root;
            m_factory = factory;
        }

        public void SayHello()
        {
            Console.WriteLine("Other.SayHello(): Hello");
            Console.WriteLine("Our IScopeRoot has GUID: {0}", m_root.Guid);
            Console.WriteLine("Our IFactory has GUID:   {0}", m_factory.Guid);
        }
    }

    public class MainClass
    {
        public static void Main(string[] args)
        {
            var kernel = new StandardKernel();

            kernel.Bind<IScopeRoot>().To<ScopeRoot>().DefinesNamedScope("RootScope");
            kernel.Bind<IFactory>().To<Factory>().InNamedScope("RootScope");
            kernel.Bind<IOther>().To<Other>().InNamedScope("RootScope");

            var root = kernel.Get<IScopeRoot>();
            root.DoSomething();

            Console.ReadKey();
        }
    }
}

In this example, Other is receiving the same instance of Factory as the root does, but a new instance of ScopeRoot is created instead of injecting the existing instance that defined the named scope.

How can I access the root of the named scope in a factory? Please note that this example is simplified. In reality, there are several layers of objects between the scope root and the factory method, so I cannot simply pass this to the factory.


Solution

  • Yes you're right, out of the box Ninject can't do .DefinesNamedScope().InNamedScope(). Except maybe for late "creation" (factory, lazy) this couldn't work anyway, because it would create a cyclic dependency.

    The simplest way to achieve what you want is to create a "root of the root"... well just one class ActualRoot which is bound with DefinesNamedScope() and gets an IRootScope injected, which again will be bound with .InNamedScope(). The bad thing about this is, that you will need to inject/Get<> an ActualRoot instead of a IRootScope.

    As far as i remember, what you can also do instead, is:

    Bind<IRootScope>().To<RootScope>()
        .InNamedScope(scopeName);
    

    and then retrieve it as follows:

    IResolutionRoot.Get<IRootScope>(new NamedScopeParameter(scopeName));
    

    This way you don't need a DefinesNamedScope().