在Startup类添加含UserManager的Singleton服务时遇作用域解析错误
你遇到的InvalidOperationException: Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.UserManager1[ibasis_api.Areas.Identity.Data.ibasis_apiUser]' from root provider`错误,核心原因是服务生命周期不匹配:
UserManager<TUser>是ASP.NET Core Identity默认注册的Scoped服务(每个请求创建一次实例)- 而你把依赖它的
IJWTAuthenticationManager和ITokenRefresher注册成了Singleton服务(应用启动时创建,一直存活到应用关闭)
Singleton服务的生命周期远长于Scoped服务,.NET Core的依赖注入容器禁止从根容器(Singleton所属的容器)解析Scoped服务,避免出现Scoped服务被长期持有、导致状态污染或资源泄漏的问题。
最推荐的解决方案:调整服务生命周期为Scoped
将IJWTAuthenticationManager和ITokenRefresher的注册从AddSingleton改为AddScoped,让它们和UserManager的生命周期保持一致:
修改Startup类中对应的服务注册代码:
// RefreshTokenGenerator是无状态的,可以保持Singleton,也可以改成Scoped,不影响 services.AddSingleton<IRefreshTokenGenerator, RefreshTokenGenerator>(); // 把下面两个服务改成Scoped services.AddScoped<IJWTAuthenticationManager>(x => new JWTAuthenticationManager( tokenKey, x.GetService<IRefreshTokenGenerator>(), x.GetService<UserManager<ibasis_apiUser>>())); services.AddScoped<ITokenRefresher>(x => new TokenRefresher( key, x.GetService<IJWTAuthenticationManager>(), x.GetService<UserManager<ibasis_apiUser>>()));
这样修改后,每个请求会创建一个IJWTAuthenticationManager和ITokenRefresher的实例,它们可以正常获取当前请求的UserManager实例,符合ASP.NET Core处理用户请求的设计模式。
备选方案:若必须使用Singleton(不推荐)
如果因为特殊需求必须保持IJWTAuthenticationManager为Singleton,可以通过注入IServiceScopeFactory手动创建Scoped来获取UserManager:
步骤1:修改JWTAuthenticationManager的构造函数
public class JWTAuthenticationManager : IJWTAuthenticationManager { private readonly string tokenKey; private readonly IRefreshTokenGenerator refreshTokenGenerator; private readonly IServiceScopeFactory _scopeFactory; // 注入Scope工厂 public JWTAuthenticationManager( string tokenKey, IRefreshTokenGenerator refreshTokenGenerator, IServiceScopeFactory scopeFactory) { this.tokenKey = tokenKey; this.refreshTokenGenerator = refreshTokenGenerator; _scopeFactory = scopeFactory; }
步骤2:在需要使用UserManager的方法中创建Scope
public async Task<AuthenticationResponse> Authenticate(string username, string password) { using (var scope = _scopeFactory.CreateScope()) // 创建Scoped容器 { var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ibasis_apiUser>>(); var user = await userManager.FindByNameAsync(username); if(user != null && !await userManager.CheckPasswordAsync(user, password)) { return null; } var token = GenerateTokenString(username, DateTime.UtcNow); var refreshToken = refreshTokenGenerator.GenerateToken(); user.RefreshTokens = refreshToken; await userManager.UpdateAsync(user); return new AuthenticationResponse { JwtToken = token, RefreshToken = refreshToken }; } // 自动释放Scope,回收Scoped服务 }
同样的,TokenRefresher也需要做类似的修改,注入IServiceScopeFactory来动态获取UserManager。
注意:这种方式需要手动管理Scope的创建和释放,容易出错,且不符合ASP.NET Core处理用户请求的常规模式,仅在特殊场景下使用。
额外检查点
- 确认你的
IdentityContext注册正确,你代码中同时调用了AddIdentityCore和AddIdentity,可以简化为只调用AddIdentity,因为AddIdentity已经包含了AddIdentityCore的功能 RefreshTokenGenerator如果是无状态的,保持Singleton是没问题的,不会影响依赖注入。
内容的提问来源于stack exchange,提问作者user964787

