Reputation: 1592
I have multiple classes (A
, B
and C
) each extends IdentityUser<Guid>
. I also have a class called UserRole
which extends IdentityRole<Guid>
.
The following is my DbContext
:
public sealed class EntityDbContext: DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
}
I added identities to IServiceCollection
:
services
.AddIdentityCore<A>()
.AddEntityFrameworkStores<EntityDbContext>()
.AddRoles<UserRole>()
.AddUserStore<AUserStore>()
// .AddRoleStore<TRoleStore>()
.AddDefaultTokenProviders();
// Same for B, C
I also have the following stores:
public class AUserStore : UserStore<A, UserRole, EntityDbContext, Guid> { }
public class BUserStore : UserStore<B, UserRole, EntityDbContext, Guid> { }
public class CUserStore : UserStore<C, UserRole, EntityDbContext, Guid> { }
The following is the error I'm getting:
Specified argument was out of the range of valid values. (Parameter 'instance 'AUserStore' with ReturnType AUserStore cannot be cast to IUserStore')
I don't know if what I'm doing is possible or not. Thanks for any help or hint.
Update
I think I got it working:
class GenericUserRoleStore : RoleStore<UserRole, EntityDbContext, Guid> { }
services.AddIdentity<A, UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<AUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<B>()
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<BUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<C>()
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<CUserStore>()
.AddRoleStore<GenericUserRoleStore>();
Upvotes: 4
Views: 5594
Reputation: 2508
Both comments on AddIdentity
and AddIdentityCore
have this:
Adds and configures the identity system for the specified User and Role types.
and,
Review the default code from project template:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
....
}
I would say: IdentityFramework got confused when you register multiple identity types to it, but we do need it.
I believe what you are looking for are these posts:
you have 3 above normal
options to map
your any UserType
data to database. and the 1st options give you best performance, but give you very messive datatable
when your usertypes are pretty complex. you would choose either of them for your real project as a balance.
Here is sample code with 1st approach:
public class ApplicationUser : IdentityUser<int>
{
public ApplicationUser() : base()
{
UserRoles = new HashSet<ApplicationUserRole>();
}
public int YearsOfExperience { get; set; }
[InverseProperty("User")]
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ProjectManager : ApplicationUser
{
public bool Talktive { get; set; }
}
public class Developer : ApplicationUser
{
public bool IsCSharper { get; set; }
}
public class Tester : Developer
{
public bool WhiteBox { get; set; }
}
public class Documenter : Tester
{
public List<string> Languages { get; set; } = new List<string>();
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
//get following users directly by following properties
public DbSet<ProjectManager> ProjectManagers { get; set; }
public DbSet<Developer> Developers { get; set; }
public DbSet<Tester> Testers { get; set; }
public DbSet<Documenter> Documenters { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//prevent creating tables for following usertypes
builder.Ignore<ProjectManager>();
builder.Ignore<Developer>();
builder.Ignore<Tester>();
builder.Ignore<Documenter>();
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>(entity =>
{
entity.HasMany(u => u.UserRoles).WithOne(x => x.User).HasForeignKey(c => c.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//tell database to use this column as Discriminator
entity.HasDiscriminator<string>("UserType");
});
builder.Entity<ApplicationRole>(entity =>
{
entity.HasKey(x => x.Id);
});
builder.Entity<ApplicationUserRole>(entity =>
{
entity.HasKey(c => new { c.UserId, c.RoleId });
entity.HasOne(x => x.Role).WithMany(x => x.UserRoles).HasForeignKey(x => x.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
entity.HasOne(x => x.User).WithMany(x => x.UserRoles).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
});
}
}
when you need your users:
var allUsers = await _dbContext.Users.ToListAsync();
var allProjectManagers = await _dbContext.ProjectManagers.ToListAsync();
var allDevelopers = await _dbContext.Developers.ToListAsync();
var allTesters = await _dbContext.Testers.ToListAsync();
The next thing you want to configure is UserManager, instead of IUserStore.
public class ApplicationUserManager<TUser, TRole>
where TUser : ApplicationUser
where TRole : ApplicationRole
{
private readonly ApplicationDbContext _context;
private readonly UserManager<TUser> _userManager;
private readonly RoleManager<TRole> _roleManager;
public ApplicationUserManager(ApplicationDbContext context,
UserManager<TUser> userManager,
RoleManager<TRole> roleManager)
{
_context = context;
_userManager = userManager;
_roleManager = roleManager;
}
//customize your own base logics here.
}
public class DeveloperUserManager : ApplicationUserManager<Developer, ApplicationRole>
{
}
public class DocumenterUserManager : ApplicationUserManager<Documenter, ApplicationRole>
{
}
Enjoy it.
Upvotes: 6