Search code examples
c#asp.netasp.net-identityninjectninject.web.mvc

ArgumentNull in ConfigureAuth - race condition?


Using ASP.NET Identity with Ninject I have this in my Startup.Auth.cs. It's mostly boilerplate:

private static StandardKernel _kernel;

private static StandardKernel CreateKernel()
{
    if (_kernel == null)
    {
        System.Diagnostics.Debug.WriteLine("Creating Kernel");
        _kernel = new StandardKernel();
        _kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

        _kernel.Load(new Services.ServiceModule());
    }
    return _kernel;
}

// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
    // Configure the db context, user manager and signin manager to use a single instance per request
    //app.CreatePerOwinContext(CreateKernel);
    app.UseNinjectMiddleware(CreateKernel);
    app.CreatePerOwinContext<MyApp.Dal.IDataAccessService>(() => 
    {
        // this occassionally throws a ArgumentNullException 
        // and _kernel is null in the debugger...?
        return _kernel.Get<MyApp.Dal.IDataAccessService>();
    });
    //... lots more config stuff
}

The problem is that this line:

return _kernel.Get<MyApp.Dal.IDataAccessService>();

Will occassionally throw a ArgumentNullException because _kernel is null. This seems to happen when I first start up my web app especially if I close the page in my browser before it's finished loading and open another. I believe it's a race condition, but I can figure out extactly what the sequence of events here is and how to correctly synchronize it.

I added a try catch around the offending line so I could see what thread it was executing on (and added the same information to the Creating Kernel debug line and I see this:

Creating Kernel on 7

And

Exception thrown: 'System.ArgumentNullException' in Ninject.dll

Exception caught on 8: Cannot be null

So clearly the problem is that the CreateKernel is on a different thread to the attempt to Get the instance of MyApp.Dal.IDataAccessService. What's the right way to sync this up?

Stack Trace:

   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Ninject.KernelBase.Load(IEnumerable`1 m)
   at Ninject.ModuleLoadExtensions.Load(IKernel kernel, INinjectModule[] modules)
   at MyApp.Startup.CreateKernel() in C:\Users\matt.burland\Documents\Visual Studio 2015\Projects\MyApp\Trunk\MyApp\MyApp\App_Start\Startup.Auth.cs:line 33
   at MyApp.Startup.<>c.<ConfigureAuth>b__3_0() in C:\Users\matt.burland\Documents\Visual Studio 2015\Projects\MyApp\Trunk\MyApp\MyApp\App_Start\Startup.Auth.cs:line 52
   at Owin.AppBuilderExtensions.<>c__DisplayClass1`1.<CreatePerOwinContext>b__0(IdentityFactoryOptions`1 options, IOwinContext context)
   at Microsoft.AspNet.Identity.Owin.IdentityFactoryProvider`1.Create(IdentityFactoryOptions`1 options, IOwinContext context)
   at Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware`2.<Invoke>d__0.MoveNext()

Solution

  • Try using a lock to mitigate the race condition. Also when calling from the expression there still exists a chance that the kernel can be null when it is called so use the method just in case.

    private static StandardKernel _kernel;
    private static object syncLock = new object();
    
    private static StandardKernel GetKernel() {
        if (_kernel == null) {
            lock(syncLock) {
                if (_kernel == null) {
                    System.Diagnostics.Debug.WriteLine("Creating Kernel");
                    _kernel = new StandardKernel();
                    _kernel.Bind<IHttpModule().To<HttpApplicationInitializationHttpModule>();
                    _kernel.Load(new Services.ServiceModule());
                }
            }
        }
        return _kernel;
    }
    
    
    public void ConfigureAuth(IAppBuilder app) {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.UseNinjectMiddleware(GetKernel);
        app.CreatePerOwinContext<MyApp.Dal.IDataAccessService>(() => 
        {
           return GetKernel().Get<MyApp.Dal.IDataAccessService>();
        });
        //... lots more config stuff
    }