简体   繁体   中英

ASP.NET Core Identity Add custom user roles on application startup

In an ASP.NET Core application, I want to create certain roles as a basis to manage different user-permissions. Sadly, the documentation inform detailled how to use custom roles eg in controllers/actions, but not how to create them. I found out that I can use RoleManager<IdentityRole> for this, where the instance gets automatically injected in a controller-constructor, when its defined and ASP.NET Core identity is registered in the application.

This let me add a custom role like this:

var testRole = new IdentityRole("TestRole");
if(!roleManager.RoleExistsAsync(testRole.Name).Result) {
    roleManager.CreateAsync(testRole);
}

It works and create the role in the database. But this check will always create overhead on the database, calling the specific controller/action. So I want to check once after my application has started, if the custom role exists and add them. The ConfigureServices method in Startup.cs seems good for this.

But: How can I create a instance of the RoleManager<IdentityRole> class for doing this? I would like to use a best practice approach here and not messing around by creating depending instances on my own, which seems to cause a lot of work since its not good documentated and will surely not follow best practices, since ASP.NET Core is using dependency injection for things like this (which is also reasonable in my oppinion).

In other words: I need to use dependeny injection outside of a controller.

Here is an example for your needs that migrates and seeds the database on startup:

Create a static class:

public static class RolesData
{
    private static readonly string[] Roles = new string[] {"Administrator", "Editor", "Subscriber"};

    public static async Task SeedRoles(IServiceProvider serviceProvider)
    {
        using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var dbContext = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

            if (dbContext.Database.GetPendingMigrations().Any())
            {
                await dbContext.Database.MigrateAsync();

                var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();

                foreach (var role in Roles)
                {
                    if (!await roleManager.RoleExistsAsync(role))
                    {
                        await roleManager.CreateAsync(new IdentityRole(role));
                    }
                }
            }
        }
    }
}

And in Startup.cs :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    RolesData.SeedRoles(app.ApplicationServices).Wait();
}

I would prefer to seed the data only if there are no roles already inserted in the database. In other word store the roles only when the application is running for the first time:

public static class RolesData
{
    private static readonly string[] Roles = new string[] { "Administrator", "Editor", "Subscriber" };

    public static async Task SeedRoles(IServiceProvider serviceProvider)
    {
        using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var dbContext = serviceScope.ServiceProvider.GetService<AppDbContext>();

            if (!dbContext.UserRoles.Any())
            {
                var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

                foreach (var role in Roles)
                {
                    if (!await roleManager.RoleExistsAsync(role))
                    {
                        await roleManager.CreateAsync(new IdentityRole(role));
                    }
                }
            }

        }
    }
}

And on Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    RolesData.SeedRoles(app.ApplicationServices).Wait();
}

Just in case anyone comes here looking for a .NET 5 solution to this, here it is:

Everything is almost the same as @Millan Sanchez's answer, apart from the bit in the Configure method...

I was getting

One or more errors occurred. (Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.RoleManager`1[Microsoft.AspNetCore.Identity.IdemtityRole]' from root provider.)' 

After another bit of searching I came across this StackOverflow answer

Essentially, all I needed to do was add in IServiceProvider into the parameters of Configure method, and pass that into my call to SeedUsers, instead of app.applicationServices

Full Code:

public static class RolesData
{
    private static readonly string[] Roles = new string[] { "Administrator", "Editor", "Subscriber" };

    public static async Task SeedRoles(IServiceProvider serviceProvider)
    {
        using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var dbContext = serviceScope.ServiceProvider.GetService<AppDbContext>();

            if (!dbContext.UserRoles.Any())
            {
                var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

                foreach (var role in Roles)
                {
                    if (!await roleManager.RoleExistsAsync(role))
                    {
                        await roleManager.CreateAsync(new IdentityRole(role));
                    }
                }
            }

        }
    }
}

And on Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
IServiceProvider serviceProvider)
{
    ...

    RolesData.SeedRoles(serviceProvider).Wait();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM