如何使用Code First Migration和Identity ASP.NET Core进行用户和角色的种子数据设置

68

我创建了一个新的干净的asp.net 5项目(rc1-final)。使用身份验证,我只有ApplicationDbContext.cs文件,其中包含以下代码:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        // On event model creating
        base.OnModelCreating(builder);
    }
}
请注意 ApplicationDbContext 使用 IdentityDbContext 而不是 DbContext。
有一个 IdentityConfig.cs 文件。我需要将经典的 protected override void Seed 放在哪里以创建角色和用户(如果不存在)?
12个回答

90

我做这个的方法是在models命名空间中创建一个类。

public class SampleData
{
    public static void Initialize(IServiceProvider serviceProvider)
    {
        var context = serviceProvider.GetService<ApplicationDbContext>();

        string[] roles = new string[] { "Owner", "Administrator", "Manager", "Editor", "Buyer", "Business", "Seller", "Subscriber" };

        foreach (string role in roles)
        {
            var roleStore = new RoleStore<IdentityRole>(context);

            if (!context.Roles.Any(r => r.Name == role))
            {
                roleStore.CreateAsync(new IdentityRole(role));
            }
        }


        var user = new ApplicationUser
        {
            FirstName = "XXXX",
            LastName = "XXXX",
            Email = "xxxx@example.com",
            NormalizedEmail = "XXXX@EXAMPLE.COM",
            UserName = "Owner",
            NormalizedUserName = "OWNER",
            PhoneNumber = "+111111111111",
            EmailConfirmed = true,
            PhoneNumberConfirmed = true,
            SecurityStamp = Guid.NewGuid().ToString("D")
        };


        if (!context.Users.Any(u => u.UserName == user.UserName))
        {
            var password = new PasswordHasher<ApplicationUser>();
            var hashed = password.HashPassword(user,"secret");
            user.PasswordHash = hashed;

            var userStore = new UserStore<ApplicationUser>(context);
            var result = userStore.CreateAsync(user);

        }

        AssignRoles(serviceProvider, user.Email, roles);

        context.SaveChangesAsync();
    }

    public static async Task<IdentityResult> AssignRoles(IServiceProvider services, string email, string[] roles)
    {
        UserManager<ApplicationUser> _userManager = services.GetService<UserManager<ApplicationUser>>();
        ApplicationUser user = await _userManager.FindByEmailAsync(email);
        var result = await _userManager.AddToRolesAsync(user, roles);

        return result;
    }

}

要在启动时运行此代码。在Startup.cs中,在路由配置之后的configure方法末尾添加以下代码,如Stafford Williams之前所说。

SampleData.Initialize(app.ApplicationServices);

1
我在寻找一种方法来让一个用户拥有固定的ID。当您提交某个ApplicationUser实例进行创建时,UserManager会分配一个新的ID。您的方法使我能够将ID设置为某个固定值-感谢您的努力 :) - intertag
4
这段代码存在一些异步问题(例如,不能保证userStore.CreateAsync在调用.AddToRolesAsync之前已经完成),但是这个概念是优秀的。 - Michal Ja
7
这让我遇到了一个关于在ApplicationDbContext中使用作用域服务的错误,或者类似的问题... - Ortund
3
出现错误,提示非泛型方法“IServiceProvider.GetService(Type)”不能与类型参数一起使用。 - Hamza Khanzada
4
@HamzaKhanzada,GetService方法有两种。请使用Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions中的public static T GetService<T> - Abdul-Razak Adam
显示剩余5条评论

48

您可以在 IdentityDbContext.cs 文件的 OnModelCreating() 方法中种子化用户和角色,如下所示。请注意,键必须预先定义以避免每次执行此方法时播种新的用户和角色。

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //Seeding a  'Administrator' role to AspNetRoles table
        modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole {Id = "2c5e174e-3b0e-446f-86af-483d56fd7210", Name = "Administrator", NormalizedName = "ADMINISTRATOR".ToUpper() });


        //a hasher to hash the password before seeding the user to the db
        var hasher = new PasswordHasher<IdentityUser>();


        //Seeding the User to AspNetUsers table
        modelBuilder.Entity<IdentityUser>().HasData(
            new IdentityUser
            {
                Id = "8e445865-a24d-4543-a6c6-9443d048cdb9", // primary key
                UserName = "myuser",
                NormalizedUserName = "MYUSER",
                PasswordHash = hasher.HashPassword(null, "Pa$$w0rd")
            }
        );


        //Seeding the relation between our user and role to AspNetUserRoles table
        modelBuilder.Entity<IdentityUserRole<string>>().HasData(
            new IdentityUserRole<string>
            {
                RoleId = "2c5e174e-3b0e-446f-86af-483d56fd7210", 
                UserId = "8e445865-a24d-4543-a6c6-9443d048cdb9"
            }
        );
        

    }

3
这个对我有用,而置顶的那个被接受的答案则没有。谢谢。 - Venugopal M
2
这也是针对.NET 5.0的正确答案。 - display-name
4
不建议这样做,微软不推荐。 - Jonathan Daniel
2
@HeribertoLugo 我在引用微软的文字: "如果您的场景包括以下任何一项,则建议使用最后一节中描述的自定义初始化逻辑:...需要调用外部API的数据,例如ASP.NET Core Identity角色和用户创建"。 - Jonathan Daniel
2
我认为这个答案的问题在于每当添加新迁移时,它都会更新并发戳。虽然这样做可以解决问题,但并不推荐。 - Lukasz Pomianowski
显示剩余7条评论

35

截至本文撰写时,尚未安装插件以种子数据库,但您可以创建一个类并将其添加到容器中,在应用程序启动时执行相同的操作,以下是我如何实现的,首先创建一个类:

public class YourDbContextSeedData
{
    private YourDbContext _context;

    public YourDbContextSeedData(YourDbContext context)
    {
        _context = context;
    }

    public async void SeedAdminUser()
    {
        var user = new ApplicationUser
        {
            UserName = "Email@email.com",
            NormalizedUserName = "email@email.com",
            Email = "Email@email.com",
            NormalizedEmail = "email@email.com",
            EmailConfirmed = true,
            LockoutEnabled = false,
            SecurityStamp = Guid.NewGuid().ToString()
        };

        var roleStore = new RoleStore<IdentityRole>(_context);

        if (!_context.Roles.Any(r => r.Name == "admin"))
        {
            await roleStore.CreateAsync(new IdentityRole { Name = "admin", NormalizedName = "admin" });
        }

        if (!_context.Users.Any(u => u.UserName == user.UserName))
        {
            var password = new PasswordHasher<ApplicationUser>();
            var hashed = password.HashPassword(user, "password");
            user.PasswordHash = hashed;
            var userStore = new UserStore<ApplicationUser>(_context);
            await userStore.CreateAsync(user);
            await userStore.AddToRoleAsync(user, "admin");
        }

        await _context.SaveChangesAsync();
    }

Startup.cs类的ConfigureServices方法中注册类型:

services.AddTransient<YourDbContextSeedData>();

YourDbContextSeedData类传递给您的Startup.cs类的Configure方法并使用它:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, YourDbContextSeedData seeder)
{
  seeder.SeedAdminUser();
}

1
在我的核心2应用程序中,我花费了许多时间尝试为管理员级别帐户提供种子,但在调用AddToRoleAsync方法时,所有其他方法都失败了。经过略微修改您的帖子后,终于为我完成了这项任务。非常感谢! - Nathan
这很有帮助,谢谢 @ Hamid。我建议其他人实际从Program.Main()调用初始化程序-请参见此答案以获取解释。在启动期间将初始化移出后,我的应用程序无法一致地加载。根据此帖子,您可能还想考虑使用UserManagerRoleManager进行验证。 - ChiefMcFrank
请注意,调用:await userStore.AddToRoleAsync(user, "admin"); 中的 "admin" 是角色的规范化值,因此如果您的角色不同,请确保它反映了规范化角色中指定的值。 - bnns
1
在EF6中让它工作了。我的问题是规范化的用户名和电子邮件必须是大写字母! - Shane

5
如果您遇到异步问题,请尝试以下代码:
    protected override void Seed(ApplicationDbContext context)
    {
        //  This method will be called after migrating to the latest version.

        string[] roles = new string[] { "Admin", "User" };
        foreach (string role in roles)
        {
            if (!context.Roles.Any(r => r.Name == role))
            {
                context.Roles.Add(new IdentityRole(role));
            }
        }

        //create user UserName:Owner Role:Admin
        if (!context.Users.Any(u => u.UserName == "Owner"))
        {
            var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
            var user = new ApplicationUser
            {
                FirstName = "XXXX",
                LastName = "XXXX",
                Email = "xxxx@example.com",
                UserName = "Owner",
                PhoneNumber = "+111111111111",
                EmailConfirmed = true,
                PhoneNumberConfirmed = true,
                SecurityStamp = Guid.NewGuid().ToString("D"),
                PasswordHash = userManager.PasswordHasher.HashPassword("secret"),
                LockoutEnabled = true,
            };
            userManager.Create(user);
            userManager.AddToRole(user.Id, "Admin");
        }            

        context.SaveChanges();
    }

4

我的方式:

  1. Create Class in models folder

    public static class ModelBuilderExtensions
      {
    
         public static void Seed(this ModelBuilder builder)
         {
    
        // Seed Roles
    
        List<IdentityRole> roles = new List<IdentityRole>()
        {
            new IdentityRole { Name = "Admin", NormalizedName = "ADMIN" },
            new IdentityRole { Name = "User", NormalizedName = "USER" }
        };
    
        builder.Entity<IdentityRole>().HasData(roles);
    
        // -----------------------------------------------------------------------------
    
        // Seed Users
    
        var passwordHasher = new PasswordHasher<ApplicationUser>();
    
        List<ApplicationUser> users = new List<ApplicationUser>()
        {
             // imporant: don't forget NormalizedUserName, NormalizedEmail 
                     new ApplicationUser {
                        UserName = "user2@hotmail.com",
                        NormalizedUserName = "USER2@HOTMAIL.COM",
                        Email = "user2@hotmail.com",
                        NormalizedEmail = "USER2@HOTMAIL.COM",
                    },
    
                    new ApplicationUser {
                        UserName = "user3@hotmail.com",
                        NormalizedUserName = "USER3@HOTMAIL.COM",
                        Email = "user3@hotmail.com",
                        NormalizedEmail = "USER3@HOTMAIL.COM",
                    },
        };
    
    
        builder.Entity<ApplicationUser>().HasData(users);
    
        ///----------------------------------------------------
    
        // Seed UserRoles
    
    
        List<IdentityUserRole<string>> userRoles = new List<IdentityUserRole<string>>();
    
          // Add Password For All Users
    
            users[0].PasswordHash = passwordHasher.HashPassword(users[0], "User.123");
            users[1].PasswordHash = passwordHasher.HashPassword(users[1], "User.155");
    
             userRoles.Add(new IdentityUserRole<string> { UserId = users[0].Id, RoleId = 
             roles.First(q => q.Name == "User").Id });
    
             userRoles.Add(new IdentityUserRole<string> { UserId = users[1].Id, RoleId = 
             roles.First(q => q.Name == "Admin").Id });
    
    
        builder.Entity<IdentityUserRole<string>>().HasData(userRoles);
    
    }}
    
  2. in DBContext

    public class AppDbContext : IdentityDbContext<ApplicationUser>
     {
    
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }
    
    protected override void OnModelCreating(ModelBuilder builder)
    {
        // Use seed method here
        builder.Seed();
        base.OnModelCreating(builder);
    }}
    

此解决方案将重新创建角色,因为未提供ID。然后它将更新并发戳,除非也提供了它。几乎与@hamza的答案相同。 - Lukasz Pomianowski
不完全准确。HasData 与您的模式数据配合使用。必须创建迁移才能进行任何插入、更新或删除操作。请参阅 https://dev59.com/sXIOtIcB2Jgan1znNMLh 以获取更清晰的说明。 - Adam Rodriguez

4
在aspnetcore中,有IHostedService的概念。这使得可以运行异步后台Task
@hamid-mosalla的解决方案可以变成async并从IHostedService实现中调用。
Seed类的实现可能如下:
public class IdentityDataSeeder
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<IdentityRole> _roleManager;

    public IdentityDataSeeder(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager)
    {
        _userManager = userManager;
        _roleManager = roleManager;
    }

    public async Task SeedAsync()
    {
        var superAdminRole = new IdentityRole
        {
            Id = "cac43a6e-f7bb-4448-baaf-1add431ccbbf",
            Name = "SuperAdmin",
            NormalizedName = "SUPERADMIN"
        };
        await CreateRoleAsync(superAdminRole);

        var superAdminUserPassword = "P@ssword1";
        var superAdminUser = new ApplicationUser
        {
            Id = "b8633e2d-a33b-45e6-8329-1958b3252bbd",
            UserName = "admin@example.nl",
            NormalizedUserName = "ADMIN@EXAMPLE.NL",
            Email = "admin@example.nl",
            NormalizedEmail = "ADMIN@EXAMPLE.NL",
            EmailConfirmed = true,
        };
        await CreateUserAsync(superAdminUser, superAdminUserPassword);

        var superAdminInRole = await _userManager.IsInRoleAsync(superAdminUser, superAdminRole.Name);
        if (!superAdminInRole)
            await _userManager.AddToRoleAsync(superAdminUser, superAdminRole.Name);
    }

    private async Task CreateRoleAsync(IdentityRole role)
    {
        var exits = await _roleManager.RoleExistsAsync(role.Name);
        if (!exits)
            await _roleManager.CreateAsync(role);
    }

    private async Task CreateUserAsync(ApplicationUser user, string password)
    {
        var exists = await _userManager.FindByEmailAsync(user.Email);
        if (exists == null)
            await _userManager.CreateAsync(user, password);
    }
}

这可以从 IHostedService 中调用:

public class SetupIdentityDataSeeder : IHostedService
{
    private readonly IServiceProvider _serviceProvider;
    public SetupIdentityDataSeeder(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var seeder = scope.ServiceProvider.GetRequiredService<IdentityDataSeeder>();

            await seeder.SeedAsync();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

Startup会像这样:

public void ConfigureServices(IServiceCollection services)
{
    //...

    services.AddHostedService<SetupIdentityDataSeeder>();
}

1
据我理解,IHostedService会一直在后台运行,直到应用程序进程停止。如果是这样的话,那么它就不适合执行像种子数据库这样的一次性任务。 - pnavk
@pnavk,文档指出:“托管服务在应用程序启动时激活,并在应用程序关闭时优雅地关闭。”参见微软文档 - Orhan
1
是的,这正是我的观点。托管服务用于需要在应用程序运行期间重复运行的东西。如果您想使用某些内容填充数据库(仅需要发生一次的任务),则可以使用异步支持在应用启动时执行一次,如此处所示:https://dev59.com/9K7la4cB1Zd3GeqPlvNT#52709902。 - pnavk
2
@pnavk,它不需要一直重复运行。IHostedService 用于后台任务和定时作业。在这里,它被用来作为后台任务运行代码。如果要重复运行它,您需要使用 Timer。由于这里没有这样做,因此它将在应用程序启动时运行一次。我只想将一个任务卸载到后台,并使用内置机制来完成。 - Orhan

3
这是尚未实现。作为一种解决方法,只需编写自己的类来检查数据库中实体是否存在,如果不存在,则添加它们,并从Startup.cs调用此类。

1
据微软表示,“播种代码不应是正常应用程序执行的一部分,因为这可能会在多个实例运行时引起并发问题,并且还需要应用程序具有修改数据库模式的权限。” https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding#custom-initialization-logic - Hamza Khanzada
@HamzaKhanzada 首先我会检查环境是否为开发环境,然后进行种子数据填充。接着我会创建一个单独的程序,用于生产环境的种子数据填充,需要手动运行一次。 - Jonathan Daniel

2

在Models命名空间中添加以下类。它适用于添加多个用户和角色,并且还将角色添加到现有用户(例如,Facebook登录)。可以像这样从startup.cs调用app.SeedUsersAndRoles();

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;

namespace MyApplication.Models
{
    public static class DataSeeder
    {
        public static async void SeedUsersAndRoles(this IApplicationBuilder app)
        {
            var context = app.ApplicationServices.GetService<ApplicationDbContext>();
            UserWithRoles[] usersWithRoles = {
                new UserWithRoles("Admin", new string[] { "Administrator" , "Distributor" },"somepassword"),//user and optional roles and password you want to seed 
                new UserWithRoles("PlainUser"),
                new UserWithRoles("Jojo",new string[]{"Distributor" }) //seed roles to existing users (e.g. facebook login).
            };

            foreach (var userWithRoles in usersWithRoles)
            {
                foreach (string role in userWithRoles.Roles)
                    if (!context.Roles.Any(r => r.Name == role))
                    {
                        var roleStore = new RoleStore<IdentityRole>(context);
                        await roleStore.CreateAsync(new IdentityRole(role));
                    }
                var ExistingUser = context.Users.FirstOrDefault(p => p.NormalizedUserName == userWithRoles.User.NormalizedUserName);
                if (ExistingUser == null) //the following syntax: !context.Users.FirstOrDefault(p => p.NormalizedUserName == userWithRoles.User.NormalizedUserName)) 
                                            //provokes execption:(ExecuteReader requires an open and available Connection.) 
                    await new UserStore<ApplicationUser>(context).CreateAsync(userWithRoles.User);
                await app.AssignRoles(userWithRoles); //assign also to existing users.
            }

            context.SaveChangesAsync();
        }

        public static async Task<IdentityResult> AssignRoles(this IApplicationBuilder app, UserWithRoles uWR)
        {
            UserManager<ApplicationUser> _userManager = app.ApplicationServices.GetService<UserManager<ApplicationUser>>();
            ApplicationUser user = await _userManager.FindByNameAsync(uWR.User.NormalizedUserName);
            var result = await _userManager.AddToRolesAsync(user, uWR.Roles);
            return result;
        }
    }
    public class UserWithRoles
    {
        private ApplicationUser user;
        public ApplicationUser User { get { return user; } }
        public string[] Roles { get; set; }
        public UserWithRoles(string name, string[] roles = null, string password = "secret")
        {
            if (roles != null)
                Roles = roles;
            else
                Roles = new string[] { };
            user = new ApplicationUser
            {
                Email = name + "@gmail.com", NormalizedEmail = name.ToUpper() + "@GMAIL.COM",
                UserName = name, NormalizedUserName = name.ToUpper(),
                PhoneNumber = "+1312341234",
                EmailConfirmed = true,
                PhoneNumberConfirmed = true,
                SecurityStamp = Guid.NewGuid().ToString("D"),
            };
            user.PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(user, password);
        }
    }
}

1

这是基于Muhammad Abdullah答案的解决方案。包括一些代码改进,提高了代码的可读性,并使其能够在.net core 2中运行。

 public class Seed
    {
        public static async Task Initialize(IServiceProvider serviceProvider, IConfiguration configuration)
        {
            var usrName = configuration.GetSection("Admin").GetSection("UserName").Value;
            var email = configuration.GetSection("Admin").GetSection("Email").Value;
            var pass = configuration.GetSection("Admin").GetSection("Pass").Value;
            var roles = new string[4] { OWNER, ADMIN, SENIOR, USER };

            if(await CreateUser(serviceProvider, email, usrName, pass, roles))
            {
                await AddToRoles(serviceProvider, email, roles);
            }
        }

        private static async Task<bool> CreateUser(IServiceProvider serviceProvider, string email, string usrName, string pass, string[] roles)
        {
            var res = false;

            using (var scope = serviceProvider.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<BaseContext>();

                if (!context.ApplicationUsers.Any(u => u.NormalizedUserName == usrName.ToUpper()))
                {
                    var roleStore = scope.ServiceProvider.GetService<RoleManager<IdentityRole>>();

                    foreach (string role in roles)
                    {
                        if (!context.Roles.Any(r => r.Name == role))
                        {
                            await roleStore.CreateAsync(new IdentityRole(role)).ConfigureAwait(false);
                        }
                    }

                    var user = new ApplicationUser
                    {
                        UserName = usrName,
                        Email = email,
                        EmailConfirmed = true,
                        NormalizedEmail = email.ToUpper(),
                        NormalizedUserName = usrName.ToUpper(),
                        PhoneNumber = null,
                        PhoneNumberConfirmed = true,
                        SecurityStamp = Guid.NewGuid().ToString()
                    };

                    var password = new PasswordHasher<ApplicationUser>();
                    user.PasswordHash = password.HashPassword(user, pass); ;

                    var userStore = new UserStore<ApplicationUser>(context);
                    res = (await userStore.CreateAsync(user).ConfigureAwait(false)).Succeeded;
                }

                return res;
            }
        }

        private static async Task AddToRoles(IServiceProvider serviceProvider, string email, string[] roles)
        {
            using (var scope = serviceProvider.CreateScope())
            {
                var userManager = scope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
                var usr = await userManager.FindByEmailAsync(email).ConfigureAwait(false);
                await userManager.AddToRolesAsync(usr, roles).ConfigureAwait(false);
            }           
        }
    }

1

通过使用扩展方法。

namespace Course.Repository.Utilities {
    public  static class AddRoleToAdmin {
       public static void ConfigurationUserAndRole(this ModelBuilder modelBuilder)
        {
            //Seeding a  'Administrator' role to AspNetRoles table
            modelBuilder.Entity<IdentityRole>().HasData(
                new IdentityRole()
                {
                    Id = "2c5e174e-3b0e-446f-86af-483d56fd7210",
                    Name = "Admin",
                    NormalizedName = "Admin".ToUpper()
                }
                );
            var hasher = new PasswordHasher<IdentityUser>();
            // Seeding the User to AspNetUsers table
            modelBuilder.Entity<AppUser>().HasData(
               new AppUser()
               {
                   Id= "8e445865-a24d-4543-a6c6-9443d048cdb9",
                   UserName = "Admin123@gmail.com",
                   Email = "Admin123@gmail.com",
                   NormalizedUserName = "Admin123@gmail.com".ToUpper(),
                   NormalizedEmail = "Admin123@gmail.com".ToUpper(),
                   PasswordHash = hasher.HashPassword(null, "Admin123"),
                   EmailConfirmed = true,
                   LockoutEnabled = true,
                   PhoneNumberConfirmed = true,
                   SecurityStamp = Guid.NewGuid().ToString()
               }
                );
            //Seeding the relation between our user and role to AspNetUserRoles table

            modelBuilder.Entity<IdentityUserRole<string>>().HasData(
                new IdentityUserRole<string>()
                {
                    RoleId= "2c5e174e-3b0e-446f-86af-483d56fd7210", // 2c5e174e-3b0e-446f-86af-483d56fd7210
                    UserId = "8e445865-a24d-4543-a6c6-9443d048cdb9" // 8e445865-a24d-4543-a6c6-9443d048cdb9
                }
                );
        }
    }
}

在 DbContext 中的 OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            
            base.OnModelCreating(modelBuilder);
             // Call Extension Method.
            modelBuilder.ConfigurationUserAndRole(); 
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接