3
votes

Using ASP.NET MVC5, EF6 & Ninject as Backend, AngularJS as frontend with token based auth (JWT).

We recently had to enable @ chars in the user names. Base on this answer in the Startup.cs (called by Ninject registration code, see below), we replaced

UserManagerFactory = () => new ApplicationUserManager(new UserStore<IdentityUser>(new SecurityDbContext()));

with

var userManager = new ApplicationUserManager(new UserStore<IdentityUser>(new SecurityDbContext()));
var validator = new UserValidator<IdentityUser>(userManager)
{
    AllowOnlyAlphanumericUserNames = false
};
userManager.UserValidator = validator;
UserManagerFactory = () => userManager;

This allows the registration of user name with @ signs as wished. However, logging into the application (even with "normal" usernames), became buggy: While the first login after start of the server works as usual, any subsequent login creates the following exception:

Cannot access a disposed object.
Object name: 'ApplicationUserManager'.

Detailed error message:

Source Error: 
Line 18:         public override async Task FindAsync(string userName, string password)
Line 19:         {
Line 20:             var result = await base.FindAsync(userName, password);
Line 21:             if (result == null)
Line 22:             {

Source File: 
xyz\Infrastructure\ApplicationUserManager.cs    Line: 20 

Stack Trace: 

[ObjectDisposedException: Cannot access a disposed object.Object name: 'ApplicationUserManager'.]   Microsoft.AspNet.Identity.UserManager`1.ThrowIfDisposed() +99   Microsoft.AspNet.Identity.d__15.MoveNext() +128   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +24   xyz.Infrastructure.d__0.MoveNext() in xzy\Infrastructure\ApplicationUserManager.cs:20   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +24   xyz.Infrastructure.d__0.MoveNext() in xyz\Infrastructure\ApplicationOAuthProvider.cs:39   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21   Microsoft.Owin.Security.OAuth.d__3a.MoveNext() +862   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +24   Microsoft.Owin.Security.OAuth.d__1e.MoveNext() +2335   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21   Microsoft.Owin.Security.OAuth.d__0.MoveNext() +1728   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +24   Microsoft.Owin.Security.Infrastructure.d__0.MoveNext() +664   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21   Microsoft.Owin.Security.Infrastructure.d__0.MoveNext() +937   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21   Microsoft.Owin.Security.Infrastructure.d__0.MoveNext() +937   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +22   Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow() +33   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +150   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar) +42   System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +415   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

I guess it has something to do with Ninject, so here's a bit more background: the above changed code is called from the NinjectWebCommon.cs:

kernel.Bind<IUserService>()
    .To<UserService>()
    .WithConstructorArgument("userManager", Startup.UserManagerFactory());
1

1 Answers

5
votes

Problem: This is an object lifetime error.

Solution: Replace your code with the following.

UserManagerFactory = () => 
{
    var userManager = new ApplicationUserManager(new UserStore<IdentityUser>(new SecurityDbContext()));
    var validator = new UserValidator<IdentityUser>(userManager)
    {
        AllowOnlyAlphanumericUserNames = false
    };
    userManager.UserValidator = validator;

    return userManager;
};

Explanation:

  • Every time your controller needs an IUserService, Ninject goes ahead and wants to construct a new UserService.
  • The UserService(IUserManager userManager) constructor cannot be called without a userManager, so Ninject calls Startup.UserManagerFactory(), as configured.
  • In your case, UserManagerFactory is a lambda expression that returns the captured variable userManager. This will always be the same instance.
  • The first time, this works as expected. When the request has been processed, Dispose is called on your user manager instance.
  • When the next request needs to be processed, the already disposed instance is used again. This will fail.
  • The corrected version is also a lambda expression, but this time we create a new instance every time.