如何在 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);
注意:您还可以通过注入RoleManager
和UserManager
来像用户一样种子角色。
【讨论】:
如果您使用数据库迁移方法,应注意 ApplicationDbInitializer.SeedUsers() 发生在 modelBuilder.Entity.HasData()
为其他表播种一样为用户播种?
@HamzaKhanzada 我已经更新了答案。我们可以通过OnModelCreating()
方法添加用户,但不推荐。 Microsoft 推荐是在我回答 5 个月后提出的,这就是为什么我不知道推荐并在您的评论后才知道并感谢您强调它。
你有那个来源吗?为什么 Identity 是一个外部 API?
@JonathanDaniel docs.microsoft.com/en-us/ef/core/modeling/…【参考方案2】:
实际上User
实体可以在OnModelCreating
中播种,需要考虑一件事:ID
s 应该是预定义的。如果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 SeedRoles
和 dotnet ef database update
来为插入创建和应用迁移
@YegorAndrosov 如果您想避免迁移部分,请将您的逻辑移出OnModelCreating
并使用UserManager
和RoleManager
而不是插入原始值。我仍然认为这种方式更干净,更适合播种其他类型的数据。
好的,在启动时遇到断点只是令人困惑,但在 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 播种数据
iCloud、Core Data 和副本以及如何播种初始数据?