.NET 6自定义Identity存储DI报错:部分服务无法构造
问题分析与解决方法
这个问题的核心是**.NET 6 中 ASP.NET Core Identity 的服务注册逻辑比 3.1 更严格**:当你调用AddIdentity<TUser, TRole>时,框架会自动依赖RoleManager<TRole>,而RoleManager必须依赖已注册的IRoleStore<TRole>实现。你只实现了CustomUserStore但未处理角色存储的注册,或者误触发了角色服务的依赖。
解决思路分两种场景
场景1:不需要角色功能
如果你的业务不需要角色管理,直接改用AddIdentityCore替代AddIdentity,它只会注册用户相关服务,不会触发RoleManager的依赖:
// Program.cs builder.Services.AddIdentityCore<ApplicationUser>(options => { // 配置Identity选项(如密码规则、锁定规则等) options.Password.RequireDigit = true; options.Password.RequireLowercase = true; }) .AddUserStore<CustomUserStore>() // 注册你的自定义用户存储 .AddDefaultTokenProviders(); // 保留默认的令牌提供器(如重置密码、邮箱验证)
同时检查控制器代码,确保没有注入RoleManager<ApplicationUserRole>,避免不必要的依赖解析。
场景2:需要角色功能
如果必须使用角色管理,必须实现IRoleStore<ApplicationUserRole>并注册到DI容器:
步骤1:实现自定义角色存储(基于Dapper)
public class CustomRoleStore : IRoleStore<ApplicationUserRole> { private readonly IDbConnection _dbConnection; public CustomRoleStore(IDbConnection dbConnection) { _dbConnection = dbConnection; } // 实现角色创建逻辑 public async Task<IdentityResult> CreateAsync(ApplicationUserRole role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var sql = @"INSERT INTO AspNetRoles (Id, Name, NormalizedName, ConcurrencyStamp) VALUES (@Id, @Name, @NormalizedName, @ConcurrencyStamp)"; await _dbConnection.ExecuteAsync(sql, new { role.Id, role.Name, role.NormalizedName, role.ConcurrencyStamp }); return IdentityResult.Success; } // 实现角色查询(按ID) public async Task<ApplicationUserRole> FindByIdAsync(string roleId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var sql = @"SELECT Id, Name, NormalizedName, ConcurrencyStamp FROM AspNetRoles WHERE Id = @RoleId"; return await _dbConnection.QueryFirstOrDefaultAsync<ApplicationUserRole>(sql, new { RoleId = roleId }); } // 实现角色查询(按标准化名称) public async Task<ApplicationUserRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var sql = @"SELECT Id, Name, NormalizedName, ConcurrencyStamp FROM AspNetRoles WHERE NormalizedName = @NormalizedName"; return await _dbConnection.QueryFirstOrDefaultAsync<ApplicationUserRole>(sql, new { NormalizedName = normalizedRoleName }); } // 实现其他IRoleStore接口方法(更新、删除、获取角色名称等) public Task<IdentityResult> UpdateAsync(ApplicationUserRole role, CancellationToken cancellationToken) { var sql = @"UPDATE AspNetRoles SET Name = @Name, NormalizedName = @NormalizedName, ConcurrencyStamp = @NewConcurrencyStamp WHERE Id = @Id AND ConcurrencyStamp = @OldConcurrencyStamp"; var rowsAffected = _dbConnection.Execute(sql, new { role.Id, role.Name, role.NormalizedName, OldConcurrencyStamp = role.ConcurrencyStamp, NewConcurrencyStamp = Guid.NewGuid().ToString() }); return Task.FromResult(rowsAffected > 0 ? IdentityResult.Success : IdentityResult.Failed(new IdentityError { Description = "角色更新失败,并发冲突" })); } public Task<IdentityResult> DeleteAsync(ApplicationUserRole role, CancellationToken cancellationToken) { var sql = @"DELETE FROM AspNetRoles WHERE Id = @Id AND ConcurrencyStamp = @ConcurrencyStamp"; var rowsAffected = _dbConnection.Execute(sql, new { role.Id, role.ConcurrencyStamp }); return Task.FromResult(rowsAffected > 0 ? IdentityResult.Success : IdentityResult.Failed(new IdentityError { Description = "角色删除失败,并发冲突" })); } public Task<string> GetRoleIdAsync(ApplicationUserRole role, CancellationToken cancellationToken) => Task.FromResult(role.Id); public Task<string> GetRoleNameAsync(ApplicationUserRole role, CancellationToken cancellationToken) => Task.FromResult(role.Name); public Task SetRoleNameAsync(ApplicationUserRole role, string roleName, CancellationToken cancellationToken) { role.Name = roleName; return Task.CompletedTask; } public Task<string> GetNormalizedRoleNameAsync(ApplicationUserRole role, CancellationToken cancellationToken) => Task.FromResult(role.NormalizedName); public Task SetNormalizedRoleNameAsync(ApplicationUserRole role, string normalizedName, CancellationToken cancellationToken) { role.NormalizedName = normalizedName; return Task.CompletedTask; } public void Dispose() { } }
步骤2:在Program.cs中注册角色存储和Identity服务
// 注册Dapper数据库连接 builder.Services.AddScoped<IDbConnection>(sp => new SqlConnection(builder.Configuration.GetConnectionString("DefaultConnection"))); // 注册包含角色的Identity服务 builder.Services.AddIdentity<ApplicationUser, ApplicationUserRole>(options => { // 配置Identity选项 options.Password.RequireDigit = true; options.Password.RequireLowercase = true; }) .AddUserStore<CustomUserStore>() // 注册自定义用户存储 .AddRoleStore<CustomRoleStore>() // 注册自定义角色存储 .AddDefaultTokenProviders();
关键差异说明
在ASP.NET Core 3.1中,AddIdentity默认不会强制校验RoleStore的存在(除非你显式使用RoleManager),但.NET 6优化了服务注册的校验逻辑,只要调用AddIdentity<TUser, TRole>就会自动依赖RoleManager,因此必须提供对应的IRoleStore实现。
内容的提问来源于stack exchange,提问作者cod11
相关产品推荐
相关产品推荐

