Search code examples
c#asp.net-core.net-coreidentityasp.net-core-identity

Unable to resolve service for type Identity.IdentityUserStore while attempting to activate Identity.IdentityUserManager


I am trying to use ASP.NET Core Identity 3.0 on a project with an existing DB that did not use Identity. So I need to do some customization because of that and because I want to do some more complex operations.

This is done using .NET Core 3.1

But when I run the solution right now I get these 2 exceptions:

InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.ISecurityStampValidator Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.SecurityStampValidator`1[ForumProject.Data.Identity.IdentityForumUser]': Unable to resolve service for type 'ForumProject.Data.Identity.IdentityUserStore' while attempting to activate 'ForumProject.Data.Identity.IdentityUserManager'.

InvalidOperationException: Unable to resolve service for type 'ForumProject.Data.Identity.IdentityUserStore' while attempting to activate 'ForumProject.Data.Identity.IdentityUserManager'.

I have no idea what I am doing wrong, it was working until I added the UserStore, so I must be creating it wrong, but I can't find what the problem is so far.

I have this set up in ConfigureServices in startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IPasswordHasher<IdentityForumUser>, IdentityPasswordHasher>();

    services.AddDbContext<DatabaseContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("ForumDatabase")));
    services.AddDbContext<IdentityDatabaseContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("ForumDatabase")));
    //services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
    services.AddIdentity<IdentityForumUser, IdentityForumRole>(options =>
        {
            options.SignIn.RequireConfirmedAccount = false;
            options.Password.RequiredLength = 2;
            options.Password.RequireDigit = false;
            options.Password.RequireLowercase = false;
            options.Password.RequiredUniqueChars = 0;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = false;
        })
        //.AddRoles<IdentityForumRole>()
        .AddUserStore<IdentityUserStore>()
        .AddUserManager<IdentityUserManager>()
        //.AddSignInManager<IdentitySignInManager>()
        .AddEntityFrameworkStores<IdentityDatabaseContext>();

    services.AddMvc();
    services.AddControllersWithViews();
    services.AddRazorPages();
}

And here is the IdentityForumUser, IdentityForumRole, IdentityDatabaseContext:

public class IdentityForumUser : IdentityUser<int>
{
    [Column("Username")]
    public override string UserName { get; set; }

    [Column("Password")]
    public override string PasswordHash { get; set; }

    public int ThemeId { get; set; } = 1; /// TODO: Make it set to the picked default one

    public int ChatStatus { get; set; } = 0;

    public DateTime RegistrationDate { get; set; } = DateTime.UtcNow;

    public bool DynamicMode { get; set; } = false;

    public DateTime LastActivity { get; set; } = DateTime.UtcNow;
}

public class IdentityForumRole : IdentityRole<int>
{
}

public class IdentityDatabaseContext : IdentityDbContext<IdentityForumUser, IdentityForumRole, int>
{
    public IdentityDatabaseContext(DbContextOptions<IdentityDatabaseContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<IdentityForumUser>()
            .Ignore(c => c.LockoutEnd)
            .Ignore(c => c.TwoFactorEnabled)
            .Ignore(c => c.PhoneNumberConfirmed)
            .Ignore(c => c.PhoneNumber)
            .Ignore(c => c.ConcurrencyStamp)
            .Ignore(c => c.EmailConfirmed)
            .Ignore(c => c.NormalizedEmail)
            //.Ignore(c => c.NormalizedUserName)
            .Ignore(c => c.LockoutEnabled)
            .Ignore(c => c.AccessFailedCount)
            .ToTable("Users");
        builder.Entity<IdentityForumRole>()
            .Ignore(c => c.ConcurrencyStamp)
            .ToTable("Roles");
    }
}

And here is the IdentityUserStore, IdentityUserManager:

public class IdentityUserStore : UserStore<IdentityForumUser, IdentityForumRole, IdentityDatabaseContext, int>
{
    public IdentityUserStore(IdentityDatabaseContext context, IdentityErrorDescriber describer = null) : base(context, describer)
    {
    }
}

public class IdentityUserManager : UserManager<IdentityForumUser>
{
    public IdentityUserManager(IdentityUserStore store, IOptions<IdentityOptions> optionsAccessor,
        IPasswordHasher<IdentityForumUser> passwordHasher, IEnumerable<IUserValidator<IdentityForumUser>> userValidators,
        IEnumerable<IPasswordValidator<IdentityForumUser>> passwordValidators, ILookupNormalizer keyNormalizer,
        IdentityErrorDescriber errors, IServiceProvider services, ILogger<IdentityUserManager> logger)
        : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
    {
    }

    public override bool SupportsUserClaim
    {
        get
        {
            ThrowIfDisposed();
            return false;
        }
    }
}

Here is the IdentityPasswordHasher, but only parts of it, I will comment out the logic itself, I am not sure this is relevant:

public class IdentityPasswordHasher : PasswordHasher<IdentityForumUser>
{
    private readonly PasswordHasherCompatibilityMode _compatibilityMode;

    public IdentityPasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
        : base(optionsAccessor)
    {
        var options = optionsAccessor?.Value ?? new PasswordHasherOptions();

        _compatibilityMode = options.CompatibilityMode;
    }

    public override string HashPassword(IdentityForumUser user, string password)
    {
        // custom hashing code
    }

    public override PasswordVerificationResult VerifyHashedPassword(IdentityForumUser user, string hashedPassword, string providedPassword)
    {
        // custom verification code
    }
}

Solution

  • In the constructor for IdentityUserManager, I changed it to use IUserStore<IdentityForumUser> store, instead of IdentityUserStore store(my custom class).

    This made it work and still use my custom store class. However, I am not happy that I can't be explicit as it makes the code easier to understand. If there is a way to make it explicit, please answer too.