17

My setup

Currently, I have two models that inherit from ApplicationUser, which inherits IdentityUser. The user classes are:

public abstract class ApplicationUser : IdentityUser
{
    [PersonalData]
    public string FirstName { get; set; }

    [PersonalData]
    public string LastName { get; set; }

    [NotMapped]
    public string FullName => $"{FirstName} {LastName}";
}

public class StudentUser : ApplicationUser
{
    [PersonalData]
    [Required]
    public string StudentNumber { get; set; }

    // A user belongs to one group
    public Group Group { get; set; }
}

public class EmployeeUser : ApplicationUser { }

The ApplicationUser contains shared properties, like the First and Last name. Both StudentUser and EmployeeUser have their own properties and relationships. This structure follows the Table Per Hierarchy (TPH) inheritance.

Ideally, I want to follow the Table Per Type (TPT) inheritance, because the SQL structure is better. ASP.NET Core only supports TPH natively, so that is why I follow the TPT approach.

The problem

I added the Identity service in Startup.cs:

services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

When I call UserManager<StudentUser> or UserManager<EmployeeUser>, I get the following error:

No service for type 'Microsoft.AspNetCore.Identity.UserManager`1[ClassroomMonitor.Models.StudentUser]' has been registered.

My question

Unfortunately, I can't find much about this error combined with this implementation.

Is it (even) possible to make it work this way?

Any help or thoughts are welcome.

Update 1

Manually adding the StudentUser or EmployeeUser as a scoped services does not seem to work (mentioned as the first answer).

services.AddScoped<UserManager<ApplicationUser>, UserManager<ApplicationUser>>();
// or..
services.AddScoped<UserManager<ApplicationUser>>();

This throws the following error:

InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[ClassroomMonitor.Models.StudentUser]'

Update 2

Here is a Gist to give you a better picture of the project structue:

Matthijs
  • 2,483
  • 5
  • 22
  • 33

5 Answers5

13

Ideally you would call the same identity setup for the derived user types as for the base user type.

Unfortunately AddIdentity method contains some code that prevents of using it more than once.

Instead, you could use AddIdentityCore. The role services are already registered by the AddIdentity, the only difference is that AddIdentityCore registers UserClaimsPrincipalFactory<TUser>, so in order to match AddIdentity setup it needs to be replaced with UserClaimsPrincipalFactory<TUser, TRole> via AddClaimsPrincipalFactory method.

The code looks like something like this:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddDefaultUI();

services.AddIdentityCore<StudentUser>()
    .AddRoles<IdentityRole>()
    .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<StudentUser, IdentityRole>>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddDefaultUI();

services.AddIdentityCore<EmployeeUser>()
    .AddRoles<IdentityRole>()
    .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<EmployeeUser, IdentityRole>>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddDefaultUI();

Of course you could move the common parts in a custom extension methods.

Update: Although the role services are already configured, you still need to call AddRoles in order to set correctly the Role property of the IndentityBuilder, which then is used by the AddEntityFrameworkStores.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Thanks for your answer. Unfortunately your solution results in a `NotSupportedException: Store does not implement IUserRoleStore.` – Matthijs Oct 17 '18 at 17:06
  • @Matthijs Darn identity, the register and login was working :-( See the update, now it should work. – Ivan Stoev Oct 17 '18 at 20:11
  • 1
    Thanks! That seems to do the job :-) I hope Microsoft will document this somewhere. – Matthijs Oct 18 '18 at 08:46
  • @IvanStoev Thank you very much! I've been all over the ms docs trying to find the best approach to this design and didn't find a single topic about this, it's weird that they didn't document a very common scenario. – HMZ Apr 07 '20 at 05:23
  • By the way we're dealing with same problem now, too . How did you handle controller side of more than one usermanagement usage? Did you use like below in controller constructor : UserManager userManager , UserManager userManager2 etc. How did you solve usermanager and userstore usage of different types in code ? Could you use generic type because we couldn't. – slnkykrn Nov 23 '20 at 07:46
  • @slnkykrn You have to use concrete services - `UserManager`, `UserManager` etc. because neither `UserManager` class nor `IUserStpre` interface support variance. You could eventually use generic `UserManager where TUser : ApplicationUser` in case the controllers have common generic base with the same generic type argument and constraint. But still they have to be concrete because of the forementioned reason - `UserManager` cannot be treated polymorphically as `UserManager` if that's what you are trying to do. – Ivan Stoev Nov 23 '20 at 08:24
  • thank you @IvanStoev after commented here I created an issue about this could you please share your opinion also there https://stackoverflow.com/questions/64964545/how-can-we-handle-more-than-one-user-usage-for-identity-core-usermanagement-and – slnkykrn Nov 23 '20 at 08:33
2

Tested on fresh project:

dotnet new mvc --auth Individual

Startup.cshtml

services.AddDefaultIdentity<User>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

User.cs

public class User : IdentityUser
{
    public string Test { get; set; }
}

Probably here's your problem:

_LoginPartial.cshtml

@inject SignInManager<User> SignInManager
@inject UserManager<User> UserManager

Also tested this way:

Startup.cs

services.AddDefaultIdentity<User2>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Users.cs

public class User : IdentityUser
{
    public string TestA { get; set; }
}
public class User2 : User
{
    public string TestB { get; set; }
}

_LoginPartial.cshtml

@inject SignInManager<User2> SignInManager
@inject UserManager<User2> UserManager
UbuntuCore
  • 409
  • 7
  • 20
  • That works if I register one user class. Like: User2. But I want to be able to use both. – Matthijs Oct 10 '18 at 11:11
  • @Matthijs Well, I've no idea. I'd suggest workaround like not using inheritance but something like that ``User.Role = Student`` instead. – UbuntuCore Oct 10 '18 at 11:17
  • That could be a workaround, yes. But it _should_ be possible somehow, because other answers on Stack recommend this way. I found another workaround for my situation, but I'll keep this question/bounty open for other user inputs. Thanks for thinking with me, though! – Matthijs Oct 11 '18 at 09:02
0

You are missing register DI for it.

services.AddScoped<UserManager<AppUser>, UserManager<AppUser>>()

StudentUser and EmployeeUser are similar to it

  • This gives the following error: `InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[ClassroomMonitor.Models.StudentUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[ClassroomMonitor.Models.StudentUser]'.` I had to change it to individual lines as well. – Matthijs Oct 08 '18 at 08:33
  • did you register enough `services.AddScoped, UserManager>()` `services.AddScoped, UserManager>()` `services.AddScoped, UserManager>()` – Nguyễn Thái Hải Oct 08 '18 at 08:39
  • Yes, I did, but that results in the error (first comment) – Matthijs Oct 08 '18 at 09:09
  • Did you use StudentSore? if you have, please try this `services.AddScoped, StudentUserStore>()` – Nguyễn Thái Hải Oct 08 '18 at 09:45
  • I don't use any custom storage. – Matthijs Oct 08 '18 at 09:55
0

You cannot add scoped for the different user types, you should really not have many different types which are derived from IdentityUser as it will mean you will either have incorrect types used all over the place or multiple different user tables in the database.

you should really structure your data so it has an ApplicationUser which is referenced by the employee entity (a separate entity) or the Student entity (again a separate entity). there is more of a design issue here rather than a code issue.

ASPIdentity will inject the Usermanager<ApplicationUser> when you AddIdentity<ApplicationUser, IdentityRole>, so adding more user managers will not work as expected.

The answer you must create different entities for every type of user, 1 for student, 1 for employee, etc. these will all have a Foreign Key to the user's id, and that will enable you to add multiple types of user without the need for different tables per user type.

then when you addIdentity you can register ApplicationUser (as is at the moment) then inject UserManager<ApplicationUser> which will get the user type.

Kyle B Cox
  • 421
  • 6
  • 18
0

if core>=3.0

services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<WorldContext>();
tayfun Kılıç
  • 2,042
  • 1
  • 14
  • 11