如何在DbContext构造函数注入UserManager?或无UserManager创建身份用户?
问题场景
我正在使用IdentityDbContext,并在OnModelCreating方法中通过SeedData类执行数据库初始化操作,代码如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); SeedData seedData = new(_userManager); seedData.SeedWebsiteAdmins(modelBuilder.Entity<WebsiteAdmin>()); seedData.SeedCompanyAdmins(modelBuilder.Entity<CompanyAdmin>()); }
SeedData类的构造函数需要UserManager类型参数,用于创建新用户并添加角色,核心代码如下:
public async void SeedWebsiteAdmins(EntityTypeBuilder<WebsiteAdmin> entity) { var superAdminLogin = new ApplicationUser() { Name = "Name", Email = "Email", UserType = UserType.WebsiteAdmin }; var result = await _userManager.CreateAsync(superAdminLogin, "{password}"); if (result.Succeeded) { await _userManager.AddToRoleAsync(superAdminLogin, RoleNames.WebsiteAdminRole); } var superAdmin = new WebsiteAdmin() { Id = SuperAdminId, WebsiteAdminLoginId = superAdminLogin.Id, MaxServiceDiscount = 100, }; entity.HasData(superAdmin); }
尝试在DbContext构造函数中注入UserManager时,出现以下错误:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore`3[RepositoryProject.DomainAuth.ApplicationUser,Microsoft.EntityFrameworkCore.DbContext,System.Guid]'
现寻求两种解决方案:
- 如何正确注入
UserManager至DbContext构造函数且不报错; - 是否可脱离
UserManager创建身份用户,以移除SeedData的相关依赖。
解决方案一:正确注入并规范种子数据执行时机
错误根源
注入错误的本质是依赖注入容器无法解析抽象DbContext类型——UserStore依赖的是你的具体自定义DbContext(而非基类DbContext);同时OnModelCreating是EF Core的模型构建阶段,不适合执行异步数据库操作(你的SeedWebsiteAdmins用了async void,会导致异步操作未完成就继续执行),也不适合在此阶段注入服务。
修正步骤
确保服务注册正确
在Program.cs中注册自定义DbContext和Identity服务时,明确指定DbContext类型:builder.Services.AddDbContext<YourCustomDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>() .AddEntityFrameworkStores<YourCustomDbContext>() .AddDefaultTokenProviders();将种子数据移至运行时初始化阶段
放弃在OnModelCreating中执行种子数据,改为在Program.cs的应用启动阶段通过服务作用域获取UserManager:var app = builder.Build(); // 执行种子数据初始化 using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; var userManager = services.GetRequiredService<UserManager<ApplicationUser>>(); var roleManager = services.GetRequiredService<RoleManager<IdentityRole<Guid>>>(); var dbContext = services.GetRequiredService<YourCustomDbContext>(); // 先确保数据库迁移完成 await dbContext.Database.MigrateAsync(); var seedData = new SeedData(userManager, roleManager, dbContext); await seedData.SeedWebsiteAdminsAsync(); await seedData.SeedCompanyAdminsAsync(); } app.Run();修改
SeedData类为异步安全的实现
把async void改为async Task,并直接操作DbContext而非EntityTypeBuilder(HasData是迁移种子,不适合运行时写入):public class SeedData { private readonly UserManager<ApplicationUser> _userManager; private readonly RoleManager<IdentityRole<Guid>> _roleManager; private readonly YourCustomDbContext _dbContext; public SeedData(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole<Guid>> roleManager, YourCustomDbContext dbContext) { _userManager = userManager; _roleManager = roleManager; _dbContext = dbContext; } public async Task SeedWebsiteAdminsAsync() { // 先确保角色存在 if (!await _roleManager.RoleExistsAsync(RoleNames.WebsiteAdminRole)) { await _roleManager.CreateAsync(new IdentityRole<Guid>(RoleNames.WebsiteAdminRole)); } // 检查用户是否已存在,避免重复创建 var existingUser = await _userManager.FindByEmailAsync("Email"); if (existingUser == null) { var superAdminLogin = new ApplicationUser() { Name = "Name", Email = "Email", UserName = "Email", // Identity必须设置UserName,可与Email一致 UserType = UserType.WebsiteAdmin, EmailConfirmed = true }; var result = await _userManager.CreateAsync(superAdminLogin, "{password}"); if (result.Succeeded) { await _userManager.AddToRoleAsync(superAdminLogin, RoleNames.WebsiteAdminRole); // 创建关联的WebsiteAdmin实体 var superAdmin = new WebsiteAdmin() { Id = SuperAdminId, WebsiteAdminLoginId = superAdminLogin.Id, MaxServiceDiscount = 100 }; _dbContext.WebsiteAdmins.Add(superAdmin); await _dbContext.SaveChangesAsync(); } } } }
解决方案二:脱离UserManager直接操作Identity实体
核心思路
直接使用EF Core操作Identity的底层实体(ApplicationUser、IdentityRole、IdentityUserRole),手动处理密码哈希,完全摆脱对UserManager的依赖。
实现代码
public static async Task SeedWebsiteAdminsAsync(YourCustomDbContext dbContext) { // 1. 确保管理员角色存在 var adminRole = await dbContext.Roles.FirstOrDefaultAsync(r => r.Name == RoleNames.WebsiteAdminRole); if (adminRole == null) { adminRole = new IdentityRole<Guid> { Id = Guid.NewGuid(), Name = RoleNames.WebsiteAdminRole, NormalizedName = RoleNames.WebsiteAdminRole.ToUpper(), ConcurrencyStamp = Guid.NewGuid().ToString() }; dbContext.Roles.Add(adminRole); await dbContext.SaveChangesAsync(); } // 2. 检查用户是否已存在 var existingUser = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == "Email"); if (existingUser == null) { var passwordHasher = new PasswordHasher<ApplicationUser>(); var superAdminLogin = new ApplicationUser() { Id = Guid.NewGuid(), Name = "Name", Email = "Email", NormalizedEmail = "EMAIL", UserName = "Email", NormalizedUserName = "EMAIL", UserType = UserType.WebsiteAdmin, EmailConfirmed = true, ConcurrencyStamp = Guid.NewGuid().ToString() }; // 手动生成密码哈希 superAdminLogin.PasswordHash = passwordHasher.HashPassword(superAdminLogin, "{password}"); dbContext.Users.Add(superAdminLogin); await dbContext.SaveChangesAsync(); // 3. 关联用户与角色 var userRoleLink = new IdentityUserRole<Guid> { UserId = superAdminLogin.Id, RoleId = adminRole.Id }; dbContext.UserRoles.Add(userRoleLink); await dbContext.SaveChangesAsync(); // 4. 创建关联的WebsiteAdmin实体 var superAdmin = new WebsiteAdmin() { Id = SuperAdminId, WebsiteAdminLoginId = superAdminLogin.Id, MaxServiceDiscount = 100 }; dbContext.WebsiteAdmins.Add(superAdmin); await dbContext.SaveChangesAsync(); } }
调用方式
在Program.cs中直接通过DbContext执行:
using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<YourCustomDbContext>(); await dbContext.Database.MigrateAsync(); await SeedWebsiteAdminsAsync(dbContext); }
内容的提问来源于stack exchange,提问作者siba36

