Search code examples
asp.net-mvcasp.net-mvc-5asp.net-identityasp.net-identity-2

ApplicationUserManager doesn't use my custom UserStore implementation


I created a new MVC5 Application with ASP.NET Identity. I haven't changed lot of the files. This is the error I get:

Exception when using ApplicationManager

The following changes in the project differ from the VisualStudio template:

ApplicationUserManager.cs

public class ApplicationUserManager : UserManager<User>
{
    public ApplicationUserManager(IUserStore<User> userStore)
        : base(userStore)
    {
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        // Use DI to resolve the UserStore
        var userStore = (IUserStore<User>) DependencyResolver.Current.GetService(typeof (IUserStore<User>));

        var manager = new ApplicationUserManager(userStore);

        // rest is same as in the template ...
    }
}

My DependencyResolver is Ninject. I created the following Bindings for it:

Bind<IMyDbContextFactory>().To<MyDbContextFactory>().InSingletonScope();
Bind<IUserStore<User>>().To<UserStore>().WithConstructorArgument("factory", factory => Kernel.Get<MyDbContextFactory>());

UserStore.cs

public class UserStore : IUserStore<User>,
    IUserLockoutStore<User, string>,
    IUserPasswordStore<User>,
    IUserTwoFactorStore<User, string>,
    IUserEmailStore<User>
{
    private readonly IMyDbContextFactory _factory;

    public UserStore(IMyDbContextFactory factory)
    {
        _factory = factory;
    }

    public void Dispose()
    {
        // Nothing to dispose here
    }

    public Task CreateAsync(User user)
    {
        using (var context = _factory.CreateContext())
        {
            // TODO: Validation
            context.Users.Add(user);
            var result = context.SaveChanges();
            return Task.FromResult(result);
        }
    }

    // the rest of the implementend methods from the interfaces ...
}

And for the sake of completeness the User entity, the DbContext and the factory I use to retrieve the DbContext in UserStore.cs

User.cs

public class User : IUser
{
    public string Id { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
    public object PhoneNumber { get; set; }
    public int AccessFailedCount { get; set; }
    public bool LockoutEnabled { get; set; }
    public DateTime? LockoutEndDateUtc { get; set; }
    public bool TwoFactorEnabled { get; set; }

    public virtual ICollection<UserRole> ApplicationUserRoles { get; set; }
    public virtual ICollection<UserLogin> ApplicationUserLogins { get; set; }
}

MyDbContextFactory.cs

public class MyDbContextFactory : IMyDbContextFactory
{
    public IMyDbContext CreateContext()
    {
        return new MyDbContext();
    }
}

MyDbContext.cs

public class MyDbContext : DbContext, IMyDbContext
{
    public MyDbContext()
        : base("MyDbContextConnection")
    {
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;

        Database.Initialize(true);
    }

    // DbSets, OnModelCreating and other stuff ...
}

The Exception implies that UserManager is null. But that's not true:

enter image description here

It seems that ApplicationUserManager doesn't really use (or sets) my injected UserStore. But why is that?


Solution

  • OK I found it out, but it was a tough one.

    The first method of the UserStore that gets called from the ApplicationUserManager is SetPasswordHashAsync. (Don't ask me why -.-)

    My implementation of that method returned null. That was wrong for two reasons

    1. I should not write return null; but return Task.FromResult<Task>(null);, since it's an async method. (Or throw an Exception). Because of that, I received that useless YSOD.
    2. The UserManager calls SetPasswordHashAsync first. Again, don't ask me why. Since I used the /Account/Register Action to register a new user, I simply didn't expect that the UserManager first tries to set the password. I mean, there is no user yet. That's why I want to register one. WTF?

    To summarize: The UserManager did actually use my UserStore, but not as I expected it.