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):
- Ninject v3.3.4
- 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 I've created a WebForm.aspx page but the same issue occurs for this too so it's not specific to ashx/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.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.
[Inject]
attribute in the code (this doesn't work forIHttpHandler
(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 levelIHttpHandler
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. – VinificatorNinject.Web
nuget package which no longer works withNinject.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 resultNInject.Web.HttpHandlerBase
no longer exists. Thanks – VinificatorNInject.Web 3.3.2
now fixes this problem. – Vinificator