Ninject InSingletonScope with Web Api RC
Asked Answered
D

3

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?

Dipstick answered 6/7, 2012 at 6:21 Comment(0)
C
41

use

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

and don't dispose the kernel in the NinjectDependencyScope

Chambray answered 6/7, 2012 at 7:36 Comment(11)
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?Dryclean
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.Chambray
I've been looking all over for this solution. Thanks! Perhaps this implementation of the NinjectDependencyScope should be in the WebApi extension project?Veronica
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.Hereinafter
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.Faience
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?Ope
@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.Chambray
@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"Faradic
BeginBlock ignores all configured ScopesChambray
@RemoGloor You are correct. My problem was constructor injection that caused InRequestScopes to appear as Singletons.Ope
@RemoGloor: Can you kindly confirm the NinjectScope and NinjectResolver I've hosted here:github.com/abpatel/Code-Samples/blob/master/src/… Also can holding onto the kernel and not disposing it cause a memory leak?Faradic
J
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.

Jariah answered 10/9, 2012 at 13:12 Comment(1)
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.Chambray
W
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.

Weathers answered 3/4, 2014 at 14:18 Comment(1)
Still running into the constructor of TImplement, when refreshing my page. So it still is creating new objects for some reason.Redstart

© 2022 - 2024 — McMap. All rights reserved.