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
}
}
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.