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

How do I seed user roles in Identity ASP.NET Core 2.1 (Identity Scaffolded)


How do I seed a "admin" user role when identity is scaffolded?

I would like to find my power-user account by email and seed the admin role. Most examples I find use Startup.cs, but you are supposed to use IdentityHostingStartup.cs to register identity related services.

So where/how do I inject the RoleManager and UserManager in IdentityHostingStartup? (I assume this is what I need, please let me know if there is a better way)

  public class IdentityHostingStartup : IHostingStartup
  {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) => {
                services.AddDbContext<MyWebContext>(options =>
                    options.UseSqlServer(
                        context.Configuration.GetConnectionString("MyWebContextConnection")));

                services.AddIdentity<MyWebUser, MyWebRole>()
                    .AddRoles<MyWebRole>()
                    .AddRoleManager<RoleManager<MyWebRole>>()
                    .AddDefaultUI()
                    .AddEntityFrameworkStores<MyWebContext>();

                services.Configure<IdentityOptions>(options => {
                    options.Password.RequireNonAlphanumeric = false;
                    });
            });
        }
  }

Solution

  • You cannot perform data seeding on the web host builder since at that time, the service provider has not been built yet. Instead, you will have to actually create the web host first before you can resolve any services you have registered before.

    My usual recommendation is to perform the initial seeding in the Program.cs after the WebHost is created. Following the default template, I would adjust the Main method to look like this:

    public static async Task Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();
    
        using (var scope = host.Services.CreateScope())
        {
            var roleManager = scope.ServiceProvider.GetService<RoleManager<MyWebRole>>();
    
            if (!await roleManager.RoleExistsAsync("admin"))
                await roleManager.CreateAsync(new MyWebRole { Name = "admin" });
        }
    
        await host.RunAsync();
    }
    

    So this will first create the web host, then it will create a dependency injection scope from which it will resolve a RoleManager instance. Using that manager you then can create the roles you require.

    You could also create a separate service for this, so you do not need to have all that logic inside the Program.cs but can just rely on some other service to perform the data seeding:

    public static async Task Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();
    
        using (var scope = host.Services.CreateScope())
        {
            var dataSeeder = scope.ServiceProvider.GetService<DataSeeder>();
            await dataSeeder.EnsureSeedDataAsync();
        }
    
        await host.RunAsync();
    }
    

    The DataSeeder would then be registered with the dependency injection container. And then it could take the RoleManager and other services (e.g. options, or even the database context) as a dependency and perform the seeding in the EnsureSeedDataAsync method.

    An alternative would be to use the Entity Framework Core data seeding functionality using the .HasData() method call on the model builder. This however requires fixed IDs on your role objects and you will also have to create the objects at database level instead of relying on the higher-level RoleManager.