如何在 EF Core 2.1.0 中播种管理员用户?

Posted

技术标签:

【中文标题】如何在 EF Core 2.1.0 中播种管理员用户?【英文标题】:How to seed an Admin user in EF Core 2.1.0? 【发布时间】:2022-01-20 19:54:38 【问题描述】:

我有一个使用 EF Core 2.1.0 的 ASP.NET Core 2.1.0 应用程序。

如何使用管理员用户播种数据库并赋予他/她管理员角色?我找不到这方面的任何文档。

【问题讨论】:

cloudscribe 开源项目与标准项目模板相比,提供了许多缺失的部分,包括用于管理用户、角色等的 ui marketplace.visualstudio.com/… 并且 github 上的源代码具有种子初始内容的代码 @987654322 @ 这里有文档docs.microsoft.com/en-us/ef/core/modeling/data-seeding 我认为这仅适用于播种简单实体,不适用于需要对密码进行哈希处理的ApplicationUser ...等 是的,好点。另一种选择是将 UserManager 注入Startup.Configure() 方法并运行一个任务来创建管理员用户和角色。 很好的问题,我想知道为什么反对票。 Identity 人员正在将我们从 Roles (不应该使用它)引入到 Claims 中,但在这样基本的必要性上没有任何东西不违背从 2.0 到 2.1 的更改的目的。我整天都在寻找完全相同的东西......我敢打赌,如果你能找到相关的例子和更新的文档,你可以用这个 Core 2.1.0 做一些很棒的事情。看起来他们在 2.1 中创建的东西是如此基础,以至于你别无选择,只能搭建支架并开始自定义......但谁知道......这一切都很痛苦。 【参考方案1】:

由于用户无法以正常方式在 Identity 中播种,就像其他表使用 .NET Core 2.1 的 .HasData() 播种一样。

Microsoft 建议:对于需要调用外部 API 的数据,例如 ASP.NET Core Identity 用户创建,建议使用自定义初始化逻辑。

种子角色在 .NET Core 2.1 中使用ApplicationDbContext 类中给出的以下代码:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole  Name = "Admin", NormalizedName = "Admin".ToUpper() );
    
按照以下步骤

为具有角色的用户提供种子

第 1 步:创建新类

public static class ApplicationDbInitializer

    public static void SeedUsers(UserManager<IdentityUser> userManager)
    
        if (userManager.FindByEmailAsync("abc@xyz.com").Result==null)
        
            IdentityUser user = new IdentityUser
            
                UserName = "abc@xyz.com",
                Email = "abc@xyz.com"
            ;

            IdentityResult result = userManager.CreateAsync(user, "PasswordHere").Result;

            if (result.Succeeded)
            
                userManager.AddToRoleAsync(user, "Admin").Wait();
            
               
       

第2步:现在修改Startup.cs类中的ConfigureServices方法。

修改前:

services.AddDefaultIdentity<IdentityUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

修改后:

services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

第三步:修改Startup.cs类中Configure方法的参数。

修改前:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
        //..........
    

修改后:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<IdentityUser> userManager)
    
        //..........
    

第 4 步:调用我们的 Seed (ApplicationDbInitializer) 类的方法:

ApplicationDbInitializer.SeedUsers(userManager);

注意:您还可以通过注入RoleManagerUserManager 来像用户一样种子角色

【讨论】:

如果您使用数据库迁移方法,应注意 ApplicationDbInitializer.SeedUsers() 发生在 modelBuilder.Entity().HasData() 之后。 为什么不能像使用 .NET Core 2.1 的 .HasData() 为其他表播种一样为用户播种? @HamzaKhanzada 我已经更新了答案。我们可以通过OnModelCreating() 方法添加用户,但不推荐。 Microsoft 推荐是在我回答 5 个月后提出的,这就是为什么我不知道推荐并在您的评论后才知道并感谢您强调它。 你有那个来源吗?为什么 Identity 是一个外部 API? @JonathanDaniel docs.microsoft.com/en-us/ef/core/modeling/…【参考方案2】:

实际上User 实体可以在OnModelCreating 中播种,需要考虑一件事:IDs 应该是预定义的。如果string类型用于TKey身份实体,则完全没有问题。

protected override void OnModelCreating(ModelBuilder builder)

    base.OnModelCreating(builder);
    // any guid
    const string ADMIN_ID = "a18be9c0-aa65-4af8-bd17-00bd9344e575";
    // any guid, but nothing is against to use the same one
    const string ROLE_ID = ADMIN_ID;
    builder.Entity<IdentityRole>().HasData(new IdentityRole
    
        Id = ROLE_ID,
        Name = "admin",
        NormalizedName = "admin"
    );

    var hasher = new PasswordHasher<UserEntity>();
    builder.Entity<UserEntity>().HasData(new UserEntity
    
        Id = ADMIN_ID,
        UserName = "admin",
        NormalizedUserName = "admin",
        Email = "some-admin-email@nonce.fake",
        NormalizedEmail = "some-admin-email@nonce.fake",
        EmailConfirmed = true,
        PasswordHash = hasher.HashPassword(null, "SOME_ADMIN_PLAIN_PASSWORD"),
        SecurityStamp = string.Empty
    );

    builder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string>
    
        RoleId = ROLE_ID,
        UserId = ADMIN_ID
    );

【讨论】:

为什么要预定义 ID? @Marie,HasData方法通过primary key保证实体存在,所以在没有设置ID的情况下,每次执行seed方法都会创建新的实体。 我有点假设。您可能希望将其添加到您的答案中 完美运行。如果有人需要引用 PasswordHasher,它是 NuGet 包“Microsoft.Extensions.Identity.Core”的一部分。 工作完美,除了在每次新迁移中都会生成代码以更新密码哈希值(因为每次运行都会不同)。【参考方案3】:

ASP.Net Core 3.1

这就是我使用EntityTypeBuilder 的方式:

角色配置:

public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole>

    private const string adminId = "2301D884-221A-4E7D-B509-0113DCC043E1";
    private const string employeeId = "7D9B7113-A8F8-4035-99A7-A20DD400F6A3";
    private const string sellerId = "78A7570F-3CE5-48BA-9461-80283ED1D94D";
    private const string customerId = "01B168FE-810B-432D-9010-233BA0B380E9";

    public void Configure(EntityTypeBuilder<IdentityRole> builder)
    

        builder.HasData(
                new IdentityRole
                
                    Id = adminId,
                    Name = "Administrator",
                    NormalizedName = "ADMINISTRATOR"
                ,
                new IdentityRole
                
                    Id = employeeId,
                    Name = "Employee",
                    NormalizedName = "EMPLOYEE"
                ,
                new IdentityRole
                
                    Id = sellerId,
                    Name = "Seller",
                    NormalizedName = "SELLER"
                ,
                new IdentityRole
                
                    Id = customerId,
                    Name = "Customer",
                    NormalizedName = "CUSTOMER"
                
            );
    

用户配置:

public class AdminConfiguration : IEntityTypeConfiguration<ApplicationUser>

    private const string adminId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";

    public void Configure(EntityTypeBuilder<ApplicationUser> builder)
    
        var admin = new ApplicationUser
        
            Id = adminId,
            UserName = "masteradmin",
            NormalizedUserName = "MASTERADMIN",
            FirstName = "Master",
            LastName = "Admin",
            Email = "Admin@Admin.com",
            NormalizedEmail = "ADMIN@ADMIN.COM",
            PhoneNumber = "XXXXXXXXXXXXX",
            EmailConfirmed = true,
            PhoneNumberConfirmed = true,
            BirthDate = new DateTime(1980,1,1),
            SecurityStamp = new Guid().ToString("D"),
            UserType = UserType.Administrator                
        ;

        admin.PasswordHash = PassGenerate(admin);

        builder.HasData(admin);
    

    public string PassGenerate(ApplicationUser user)
    
        var passHash = new PasswordHasher<ApplicationUser>();
        return passHash.HashPassword(user, "password");
    

为用户分配角色:

 public class UsersWithRolesConfig : IEntityTypeConfiguration<IdentityUserRole<string>>
    
        private const string adminUserId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";
        private const string adminRoleId = "2301D884-221A-4E7D-B509-0113DCC043E1";

        public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder)
        
            IdentityUserRole<string> iur = new IdentityUserRole<string>
            
                RoleId = adminRoleId,
                UserId = adminUserId
            ;

            builder.HasData(iur);
        
    

终于在 DB Context 类中:

protected override void OnModelCreating(ModelBuilder modelBuilder)

    base.OnModelCreating(modelBuilder);

    //If you have alot of data configurations you can use this (works from ASP.Net core 2.2):

    //This will pick up all configurations that are defined in the assembly
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());

    //Instead of this:
    modelBuilder.ApplyConfiguration(new RoleConfiguration());
    modelBuilder.ApplyConfiguration(new AdminConfiguration());
    modelBuilder.ApplyConfiguration(new UsersWithRolesConfig());

【讨论】:

比其他方法更好地填充用户和其他数据的好和干净的方式 我认为它在每次启动时都会执行,但我还必须运行 dotnet ef migrations add SeedRolesdotnet ef database update 来为插入创建和应用迁移 @YegorAndrosov 如果您想避免迁移部分,请将您的逻辑移出OnModelCreating 并使用UserManagerRoleManager 而不是插入原始值。我仍然认为这种方式更干净,更适合播种其他类型的数据。 好的,在启动时遇到断点只是令人困惑,但在 db 中看到空表。没有看到任何提及这适用于迁移。所以我认为它可能对其他人有帮助 不错。只需将new Guid().ToString("D") 替换为Guid.NewGuid().ToString()【参考方案4】:

这就是我最后的做法。我创建了一个DbInitializer.cs 类来播种我的所有数据(包括管理员用户)。

这是与用户帐户播种相关的方法的代码:

private static async Task CreateRole(RoleManager<IdentityRole> roleManager, 
ILogger<DbInitializer> logger, string role)

  logger.LogInformation($"Create the role `role` for application");
  IdentityResult result = await roleManager.CreateAsync(new IdentityRole(role));
  if (result.Succeeded)
  
    logger.LogDebug($"Created the role `role` successfully");
  
  else
  
    ApplicationException exception = new ApplicationException($"Default role `role` cannot be created");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(result));
    throw exception;
  


private static async Task<ApplicationUser> CreateDefaultUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string displayName, string email)

  logger.LogInformation($"Create default user with email `email` for application");

  ApplicationUser user = new ApplicationUser
  
    DisplayUsername = displayName,
    Email = email,
    UserName = email
  ;

  IdentityResult identityResult = await userManager.CreateAsync(user);

  if (identityResult.Succeeded)
  
    logger.LogDebug($"Created default user `email` successfully");
  
  else
  
    ApplicationException exception = new ApplicationException($"Default user `email` cannot be created");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
    throw exception;
  

  ApplicationUser createdUser = await userManager.FindByEmailAsync(email);
  return createdUser;


private static async Task SetPasswordForUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string email, ApplicationUser user, string password)

  logger.LogInformation($"Set password for default user `email`");
  IdentityResult identityResult = await userManager.AddPasswordAsync(user, password);
  if (identityResult.Succeeded)
  
    logger.LogTrace($"Set password `password` for default user `email` successfully");
  
  else
  
    ApplicationException exception = new ApplicationException($"Password for the user `email` cannot be set");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
    throw exception;
  

我的Program.cs 看起来像这样:

public class Program

  public static async Task Main(string[] args)
  
    var host = BuildWebHost(args);

    using (var scope = host.Services.CreateScope())
    
      var services = scope.ServiceProvider;
      Console.WriteLine(services.GetService<IConfiguration>().GetConnectionString("DefaultConnection"));
      try
      
        var context = services.GetRequiredService<PdContext>();
        var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
        var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();

        var dbInitializerLogger = services.GetRequiredService<ILogger<DbInitializer>>();
        await DbInitializer.Initialize(context, userManager, roleManager, dbInitializerLogger);
      
      catch (Exception ex)
      
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
      
    

    host.Run();
  

  public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .Build();

【讨论】:

【参考方案5】:

这是基于 .NET 6 和个人用户帐户,然后是脚手架身份。用户被创建,然后根据微软的代码收到一封确认的电子邮件。

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-6.0&tabs=visual-studio#scaffold-identity-into-a-razor-project-with-authorization

然后,您可以按照@Zubair Rana 的回答播种角色。

https://***.com/a/51571555/3850405

程序.cs:

public class Program

    public static void Main(string[] args)
    
        var host = CreateHostBuilder(args).Build();
        CreateDbAndRunMigrations(host);
        host.Run();
    

    private static void CreateDbAndRunMigrations(IHost host)
    
        using (var scope = host.Services.CreateScope())
        
            var services = scope.ServiceProvider;

            var context = services.GetRequiredService<ApplicationDbContext>();
            context.Database.Migrate();

            var userStore = services.GetRequiredService<IUserStore<ApplicationUser>>();

            var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();

            DbInitializer.Initialize(context, userManager, userStore);
        
    

DbInitializer.cs:

public static class DbInitializer

    public static void Initialize(ApplicationDbContext context, UserManager<ApplicationUser> userManager, IUserStore<ApplicationUser> userStore)
    
        if (context.Users.Any())
        
            return;   // DB has been seeded
        

        var user = Activator.CreateInstance<ApplicationUser>();

        var email = "example@example.com";

        var emailStore = (IUserEmailStore<ApplicationUser>)userStore;

        //Will not be used - Has to use Forgot Password. Last characters used to make sure password validation passes
        var password = GetUniqueKey(40) + "aA1!";

        userStore.SetUserNameAsync(user, email, CancellationToken.None).Wait();
        emailStore.SetEmailAsync(user, email, CancellationToken.None).Wait();
        var result = userManager.CreateAsync(user, password).Result;

        if (result.Succeeded)
        
            var userId = userManager.GetUserIdAsync(user).Result;
            var code =  userManager.GenerateEmailConfirmationTokenAsync(user).Result;
            userManager.ConfirmEmailAsync(user, code).Wait();
        
        else
        
            throw new Exception();
        
    
    
    private static string GetUniqueKey(int size)
    
        var chars =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!+?*~".ToCharArray();

        byte[] data = new byte[4*size];
        using (var crypto = RandomNumberGenerator.Create())
        
            crypto.GetBytes(data);
        
        StringBuilder result = new StringBuilder(size);
        for (int i = 0; i < size; i++)
        
            var rnd = BitConverter.ToUInt32(data, i * 4);
            var idx = rnd % chars.Length;

            result.Append(chars[idx]);
        

        return result.ToString();
    

【讨论】:

【参考方案6】:

如果您指的是身份用户,我们所做的方式是在 DbContext.OnModelCreating 中添加硬编码值:

builder.Entity<Role>().HasData(new Role  Id = 2147483645, Name = UserRole.Admin.ToString(), NormalizedName = UserRole.Admin.ToString().ToUpper(), ConcurrencyStamp = "123c90a4-dfcb-4e77-91e9-d390b5b6e21b" );

和用户:

builder.Entity<User>().HasData(new User
        
            Id = 2147483646,
            AccessFailedCount = 0,
            PasswordHash = "SomePasswordHashKnownToYou",
            LockoutEnabled = true,
            FirstName = "AdminFName",
            LastName = "AdminLName",
            UserName = "admin",
            Email = "admin@gmail.com",
            EmailConfirmed = true,
            InitialPaymentCompleted = true,
            MaxUnbalancedTech = 1,
            UniqueStamp = "2a1a39ef-ccc0-459d-aa9a-eec077bfdd22",
            NormalizedEmail = "ADMIN@GMAIL.COM",
            NormalizedUserName = "ADMIN",
            TermsOfServiceAccepted = true,
            TermsOfServiceAcceptedTimestamp = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
            SecurityStamp = "ce907fd5-ccb4-4e96-a7ea-45712a14f5ef",
            ConcurrencyStamp = "32fe9448-0c6c-43b2-b605-802c19c333a6",
            CreatedTime = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
            LastModified = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc)
        );

builder.Entity<UserRoles>().HasData(new UserRoles()  RoleId = 2147483645, UserId = 2147483646 );

我希望有更好/更清洁的方法来做到这一点。

【讨论】:

以上是关于如何在 EF Core 2.1.0 中播种管理员用户?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 EF 4.3 中使用带有复杂键的 AddOrUpdate 播种数据

EF Core Seeding 机制不插入数据

iCloud、Core Data 和副本以及如何播种初始数据?

如何在我的 asp.net core 3.1 应用程序中播种用户和角色?

查缺补漏系统学习 EF Core 6 - 数据查询

使用 MVVM 创建具有现有外部属性的 EF Core 实体