Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No service for type Identity.UserManager when using multiple identity users

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:

like image 552
Matthijs Avatar asked Oct 08 '18 07:10

Matthijs


2 Answers

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.

like image 145
Ivan Stoev Avatar answered Sep 22 '22 07:09

Ivan Stoev


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
like image 38
UbuntuCore Avatar answered Sep 18 '22 07:09

UbuntuCore