Search code examples
c#asp.net-coreasp.net-identity

Getting instance of RoleManager in ASP.NET Core 8 new core Identity


I am experimenting with the new identity infrastructure in an ASP.NET web API app (as per https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-8.0).

At startup, once WebApplication has been built and before running it, I would like to get instances of UserManager<AppUser> (where AppUser is just a class derived from IdentityUser) and RoleManager<IdentityRole> so that I can seed some preset user accounts.

AFAIK, the proper way of seeding data at startup is creating an extension method for IHost and call it in my Program.cs Main function, before running the app, e.g.:

public static async Task<IHost> SeedAsync(this IHost host)
{
  using var scope = host.Services.CreateScope();
  IServiceProvider serviceProvider = scope.ServiceProvider;

  // ... use services like UserManager and RoleManager to seed user accounts ...
}

Anyway, it seems that while UserManager was registered as expected, there is no available service for RoleManager: when trying to get it I get an exception "'No service for type 'Microsoft.AspNetCore.Identity.RoleManager`1[Microsoft.AspNetCore.Identity.IdentityRole]' has been registered.'".

Here is a sample repro procedure:

(1) create a new ASP.NET Core 8 Web API app. No identity selected.

(2) add packages:

  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.InMemory

(3) add AppUser.cs and AppDbContext.cs, and change Program.cs as follows:

// AppUser.cs
public class AppUser : IdentityUser
{
}

// AppDbContext.cs
public class AppDbContext : IdentityDbContext<AppUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) :
        base(options)
    { }
}

// Program.cs
public static class Program
{
    private static void ConfigureAuthServices(IServiceCollection services,
        IConfiguration configuration)
    {
        services.AddDbContext<AppDbContext>(
            options => options.UseInMemoryDatabase("Test"));

        services.AddAuthorization();

        services.AddIdentityApiEndpoints<AppUser>()
            .AddEntityFrameworkStores<AppDbContext>();
    }

    private static void ConfigureServices(IServiceCollection services,
        IConfiguration configuration)
    {
        // auth
        ConfigureAuthServices(services, configuration);

        // API controllers
        services.AddControllers();

        // Swagger/OpenAPI (https://aka.ms/aspnetcore/swashbuckle)
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();

        // database initializer
        services.AddScoped(sp =>
        {
            return new AppDatabaseInitializer(
                sp.GetRequiredService<UserManager<AppUser>>(),
                sp.GetRequiredService<RoleManager<IdentityRole>>(),
                configuration,
                sp.GetRequiredService<ILogger<AppDatabaseInitializer>>());
        });
    }

    public static async Task Main(string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        ConfigureServices(builder.Services, builder.Configuration);

        WebApplication app = builder.Build();
        app.MapGroup("/account").MapIdentityApi<AppUser>();
        app.UseSwagger();
        app.UseSwaggerUI();

        app.UseAuthorization();
        app.MapControllers();

        // testing DI for services
        using (var scope = app.Services.CreateScope())
        {
            // this works
            var um = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
            // ==> THIS THROWS
            var rm = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
        }

        // here I would call await app.SeedAsync(); using my extension method
        // for IHost, using an AppDatabaseInitializer service requiring UserManager
        // and RoleManager via DI. Yet, it would fail just like the above code.

        app.Run();
    }
}

If you now run, you will see that it throws when trying to get RoleManager (while it succeeds in getting UserManager). How am I supposed to configure this V8 Identity system so that RoleManager gets configured properly for DI?


Solution

  • Add .AddRoles<IdentityRole>() before the .AddEntityFrameworkStores<AppDbContext> like this:

    services.AddIdentityApiEndpoints<AppUser>()
       .AddRoles<IdentityRole>()
       .AddEntityFrameworkStores<AppDbContext>();
    

    Now you will be able to successfully inject the RoleManager<IdentityRole> roleManager in the constructor.