InRequestScope acting like InTransientScope
Asked Answered
V

0

6

I have an ASP.NET Web Application (.NET Framework 4.8) in which I've set up NInject but any services I set up with InRequestScope are coming through as if transient scope (i.e. new instances are created for every entity that depends a dependency on it - within the same web request).

The NuGet packages I am using are as follows (latest):

  1. Ninject v3.3.4
  2. Ninject.Web.Common v.3.32 ("Bootstrapper for web projects")

App_Start\Ninject.Web.Common.cs is present and correct and is as follows:

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(ScormWebService.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(ScormWebService.App_Start.NinjectWebCommon), "Stop")]

namespace ScormWebService.App_Start
{
    using Microsoft.Web.Infrastructure.DynamicModuleHelper;
    using Ninject;
    using Ninject.Web.Common;
    using Ninject.Web.Common.WebHost;
    using Services;
    using System;
    using System.Web;

    public static class NinjectWebCommon
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();
        public static IKernel Kernel { get; private set; }

        /// <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();
            try
            {
                RegisterServices(kernel);
                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
            kernel.Bind<IDumpConfigService>().To<DumpConfigService>().InSingletonScope();

            // These objects are created fresh for each request
            kernel.Bind<ILogService>().To<LogService>().InRequestScope();
            kernel.Bind<IDumpService>().To<DumpService>().InRequestScope();
            kernel.Bind<ISaveDataRequestReader>().To<SaveDataRequestReaderXml>().InRequestScope();
            kernel.Bind<ISaveDataResponseWriter>().To<SaveDataResponseWriterXml>().InRequestScope();
            kernel.Bind<IHttpContextAccessor>().To<HttpContextAccessor>().InRequestScope();

            Kernel = kernel;
        }
    }
}

The incoming request is actually an implementation of IHttpHandler (i.e. an ashx rather than aspx file). However, it is still a page request with a current request and an HttpContext.Current.

Here is how I am setting up the entities for the page request

public class SaveDataHandler : IHttpHandler, IRequiresSessionState
{
    /// <summary>
    /// A new handler is required for each and every incoming request
    /// </summary>
    public bool IsReusable => false;

    public SaveDataHandler()
    {
        var kernel = App_Start.NinjectWebCommon.Kernel;
        LogService = (ILogService)kernel.GetService(typeof(ILogService));
        Reader = (ISaveDataRequestReader)kernel.GetService(typeof(ISaveDataRequestReader));
        Writer = (ISaveDataResponseWriter)kernel.GetService(typeof(ISaveDataResponseWriter));
        DumpService = (IDumpService)kernel.GetService(typeof(IDumpService));
    }

}

So for example, three instances of ILogService are created per request during the SaveDataHandler constructor instead of one: SaveDataHandler itself requests it (see above) as does class DumpService : IDumpService and class SaveDataRequestReaderXml : ISaveDataRequestReader.

Can anyone provide insight as to why InRequestScope is acting like a transient scope? I suspect the cause is using a IHttpHandler (ashx) instead of Web Form (aspx) page but I can't see why that wouldn't work as HttpContext.Current is the same across the request and that is what NInject.Web.Common uses as a request scope identifier. I've created a WebForm.aspx page but the same issue occurs for this too so it's not specific to ashx/IHttpHandler requests:

namespace ScormWebService
{
    public partial class WebForm1 : Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            var kernel = App_Start.NinjectWebCommon.Kernel;
            var LogService = (ILogService)kernel.GetService(typeof(ILogService));
            var Reader = (ISaveDataRequestReader)kernel.GetService(typeof(ISaveDataRequestReader));
            var Writer = (ISaveDataResponseWriter)kernel.GetService(typeof(ISaveDataResponseWriter));
            var DumpService = (IDumpService)kernel.GetService(typeof(IDumpService));
            // At this point, three instances of LogService have been created.
        }
    }
}

Edit: I've created a fresh minimal ASP.NET Web Forms project that reproduces the problem which you can download here but all the essential elements are already described in the code above.

Thanks.

Vinificator answered 17/6, 2020 at 0:43 Comment(7)
Sorry because I know this is totally unrelated to your question but I had to say this after seeing your avatar. Manic Miner, best game ever.Frozen
Thanks @Frozen Wearing my ZX Spectrum T-Shirt today too! :-)Vinificator
I have created a fresh Minimal Web Forms project that reproduces the problem. linkVinificator
two questions about your code : why both an inject attribute and a service locator pattern in your page ? Have you tried this : https://mcmap.net/q/1918912/-ninjecting-into-a-asp-net-website-httphandler ?Maymaya
Hi @jbl, I don't think there's an [Inject] attribute in the code (this doesn't work for IHttpHandler (ashx) files in ASP.NET Web Forms. Apart from the top level, it's all constructor injected down the line. As for the Service Locators, this is only happening on the one place in the app which is the top level IHttpHandler class. I suppose I could reduce the number of service locators down from 4 to 1 if I wrap them in a proxy class that just injects those 4 but for the sake of just one place in my app where I am doing it, it hard seems worth it. Thanks.Vinificator
Hi @jbl, thanks for the link. However this is using the outdated Ninject.Web nuget package which no longer works with Ninject.Web.Common as the latter has integrated part of the former. I raised an SO question nearly a couple of years ago about this and worked out the issue (#52272985). As a result NInject.Web.HttpHandlerBase no longer exists. ThanksVinificator
Just to let everyone know, an update to NInject.Web 3.3.2 now fixes this problem.Vinificator

© 2022 - 2024 — McMap. All rights reserved.