17

I'm having some difficulty using Ninject's InSingletonScope binding with Web Api RC. No matter how I create my binding, it looks like perhaps Web Api is handling scope/lifetime instead of Ninject.

I've tried a few variations on wiring up Ninject. The most common is identical to the answer here: ASP.NET Web API binding with ninject

I've also tried this version: http://www.peterprovost.org/blog/2012/06/19/adding-ninject-to-web-api/

In both, I'm literally creating an out of the box Web Api project, then adding the Ninject packages as described in either post. Finally, I'm adding the Resolver and Scope classes, such as this for the StackOverflow version:

public class NinjectDependencyScope : IDependencyScope
{
    private IResolutionRoot resolver;

    internal NinjectDependencyScope(IResolutionRoot resolver)
    {
        Contract.Assert(resolver != null);

        this.resolver = resolver;
    }

    public void Dispose()
    {
        IDisposable disposable = resolver as IDisposable;
        if (disposable != null)
            disposable.Dispose();

        resolver = null;
    }
    public object GetService(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has already been disposed");
        return resolver.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has already been disposed");
        return resolver.GetAll(serviceType);
    }
}

and:

 public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
    private IKernel kernel;

    public NinjectDependencyResolver(IKernel kernel)
        : base(kernel)
    {
        this.kernel = kernel;
    }
    public IDependencyScope BeginScope()
    {
        return new NinjectDependencyScope(kernel.BeginBlock());
    }
}

Then, NinjectWebCommon looks like this:

using System.Web.Http;
using MvcApplication2.Controllers;

[assembly: WebActivator.PreApplicationStartMethod(typeof(MvcApplication2.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(MvcApplication2.App_Start.NinjectWebCommon), "Stop")]

namespace MvcApplication2.App_Start
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;

    public static class NinjectWebCommon
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start()
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }

        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

            // Register Dependencies
            RegisterServices(kernel);

            // Set Web API Resolver
            GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);

            return kernel;
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<ILogger>().To<Logger>().InSingletonScope();
        }
    }
}

The ILogger and Logger objects don't do anything, but illustrate the issue. Logger does Debug.Writeline so that I can see when it was instantiated. And each refresh of the page shows that it's being refreshed per call, rather than the singleton I'd hoped for. Here is a controller using the Logger:

public class ValuesController : ApiController
{
    private readonly ILogger _logger;
    public ValuesController(ILogger logger)
    {
        _logger = logger;
        _logger.Log("Logger created at " + System.DateTime.Now.ToLongTimeString());
    }
    // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
    // GET api/values/5
    public string Get(int id)
    {
        return "value";
    }
    // POST api/values
    public void Post(string value)
    {
    }
    // PUT api/values/5
    public void Put(int id, string value)
    {
    }
    // DELETE api/values/5
    public void Delete(int id)
    {
    }
}

When I put trace information into the creation of the kernel, it seems to show that the kernel is only created once. So... what am I not seeing? Why isn't the singleton persisted?

Community
  • 1
  • 1
Mike L
  • 4,693
  • 5
  • 33
  • 52

3 Answers3

41

use

public IDependencyScope BeginScope()
{
    return new NinjectDependencyScope(kernel);
}

and don't dispose the kernel in the NinjectDependencyScope

Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • 1
    Remo, thank you so much for the solution! I was also struggling with the singleton issue in web api. However, I don't think I understand your solution. Can you please elaborate? – ReVolly Aug 24 '12 at 21:25
  • 3
    Ninject itself is aware of the request duration so there is no need to create a new scope. Basically this code ignores this MVC4 feature. Only in case of WebAPI self hosting you will need to do things differently but event there activation blocks aren't the right way to go. – Remo Gloor Aug 31 '12 at 00:22
  • 7
    I've been looking all over for this solution. Thanks! Perhaps this implementation of the NinjectDependencyScope should be in the WebApi extension project? – Yngve B-Nilsen Sep 29 '12 at 07:28
  • so, am i understanding right that in MVC, we're reconstructing the Kernel with every request into the app then? Seems strange to go that route. – Richard B Dec 18 '12 at 08:20
  • That really helps! Ninject.Extenstion.WebApi (I don't remeber exact name of it) has IDependencyResolver.BeginScope as: public IDependencyScope BeginScope() { return new NinjectDependencyScope(kernel.BeginBlock()); } and it doesn't work for substititing Singleton dependencies for controllers. – Shrike Dec 18 '12 at 16:50
  • This seems to fix the singleton issue, but it appears to break `InRequestScope` bindings. I seem to get the same instance for different requests for types bound with `InRequestScope`. I would like to understand what causes that and how can the problem be resolved? – Juho Rutila Jan 25 '13 at 08:23
  • @JuhoRutila InRequestScope uses HttpContext.Current as scope. Thus, if you are doing everything else correctly and you are not self hosting, you will still get an instance per request when doing it as described above. – Remo Gloor Jan 25 '13 at 13:27
  • 3
    @Remo : Can you provide some details on your answer, such as the difference between doing "new NinjectDependencyScope(kernel); " vs. "new NinjectDependencyScope(kernel.BeginBlock()); " and the implications of not disposing the kernel in the "NinjectDependencyScope" – Abhijeet Patel Feb 02 '13 at 19:29
  • BeginBlock ignores all configured Scopes – Remo Gloor Feb 03 '13 at 20:05
  • @RemoGloor You are correct. My problem was constructor injection that caused InRequestScopes to appear as Singletons. – Juho Rutila Feb 18 '13 at 09:00
  • 1
    @RemoGloor: Can you kindly confirm the NinjectScope and NinjectResolver I've hosted here:https://github.com/abpatel/Code-Samples/blob/master/src/NinjectResolver.cs Also can holding onto the kernel and not disposing it cause a memory leak? – Abhijeet Patel Oct 24 '13 at 02:13
0

@Remo Gloor When I run your code in InMemoryHost of WebAPI and run Integration tests everything works fine and I do have singleton. If I run WebAPI solution inside VS Cassini web server first run is successful and when I click refresh I receive exception : Error loading Ninject component ICache No such component has been registered in the kernel's component container.

If I return old code with BeginBlock it works in Cassini but IsSingleton not working anymore in integration tests.

Radenko Zec
  • 7,659
  • 6
  • 35
  • 39
  • 4
    This error usually happens if objects are requested from an already disposed kernel. But I don't have any expirience running WebAPI in Cassini. BTW: Asking questions in an answers is against SO policy. Please put this into a new question or add a comment somewhere instead. – Remo Gloor Oct 01 '12 at 13:15
0

Instead of not disposing the kernel (which will not call the internal dispose) you can simply implement your own singleton:

public static class NinjectSingletonExtension
{
    public static CustomSingletonKernelModel<T> SingletonBind<T>(this IKernel i_KernelInstance)
    {
        return new CustomSingletonKernelModel<T>(i_KernelInstance);
    }
}

public class CustomSingletonKernelModel<T>
{
    private const string k_ConstantInjectionName = "Implementation";
    private readonly IKernel _kernel;
    private T _concreteInstance;


    public CustomSingletonKernelModel(IKernel i_KernelInstance)
    {
        this._kernel = i_KernelInstance;
    }

    public IBindingInNamedWithOrOnSyntax<T> To<TImplement>(TImplement i_Constant = null) where TImplement : class, T
    {
        _kernel.Bind<T>().To<TImplement>().Named(k_ConstantInjectionName);
        var toReturn =
            _kernel.Bind<T>().ToMethod(x =>
                                       {
                                           if (i_Constant != null)
                                           {
                                               return i_Constant;
                                           }

                                           if (_concreteInstance == null)
                                           {
                                               _concreteInstance = _kernel.Get<T>(k_ConstantInjectionName);
                                           }

                                           return _concreteInstance;
                                       }).When(x => true);

        return toReturn;
    }
}

And then simply use:

i_Kernel.SingletonBind<T>().To<TImplement>();

Rather then

i_Kernel.Bind<T>().To<TImplement>().InSingletonScope();


note: although it is only matters for the first request, this implementation is not thread safe.

Tomer
  • 4,382
  • 5
  • 38
  • 48
  • Still running into the constructor of `TImplement`, when refreshing my page. So it still is creating new objects for some reason. – Jaanus Aug 12 '14 at 08:41