ASP.NET Core Web API:SaveChangesAsync中Current UserName为空问题排查
问题原因及修复方案
核心原因
问题出在依赖注入生命周期不匹配或UnitOfWork实现错误,导致ApplicationDbContext中注入的ICurrentUserService无法获取当前请求的用户上下文:
- 如果
UnitOfWork或ApplicationDbContext被注册为Singleton生命周期,它们会在应用启动时就被实例化,而ICurrentUserService依赖的HttpContext是请求级的,Singleton实例无法随请求更新用户信息。 - 若
UnitOfWork自行创建ApplicationDbContext实例(而非通过DI注入),新创建的DbContext会拿到没有绑定HttpContext的ICurrentUserService实例,自然获取不到用户信息。 - 少数情况是
ICurrentUserService的实现存在问题,比如缓存了用户信息,没有实时从HttpContext获取。
修复步骤
1. 统一服务生命周期
确保所有相关服务都注册为Scoped(请求级),因为它们都依赖当前请求的HttpContext:
// 注册HttpContextAccessor(必须) builder.Services.AddHttpContextAccessor(); // 注册CurrentUserService为Scoped builder.Services.AddScoped<ICurrentUserService, CurrentUserService>(); // 注册DbContext为Scoped(默认就是Scoped,可显式声明) builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // 注册UnitOfWork为Scoped builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
2. 修正UnitOfWork实现
确保UnitOfWork通过构造函数注入ApplicationDbContext,而非自行实例化:
public class UnitOfWork : IUnitOfWork { private readonly ApplicationDbContext _dbContext; // 通过DI注入DbContext,保证拿到的是当前请求的Scoped实例 public UnitOfWork(ApplicationDbContext dbContext) { _dbContext = dbContext; } public void Save() { _dbContext.SaveChanges(); } public async Task SaveAsync() { await _dbContext.SaveChangesAsync(); } }
3. 确保CurrentUserService正确从HttpContext获取信息
检查ICurrentUserService的实现,确保实时从当前请求的HttpContext读取用户信息,而非缓存:
public class CurrentUserService : ICurrentUserService { private readonly IHttpContextAccessor _httpContextAccessor; public CurrentUserService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } // 实时从HttpContext获取,每次调用都会取当前请求的用户信息 public string? UserId => _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); public string? UserName => _httpContextAccessor.HttpContext?.User.Identity?.Name; }
4. 验证DbContext的审计逻辑
确保重写SaveChanges/SaveChangesAsync时,每次都会重新获取ICurrentUserService的用户信息,而非提前缓存:
public class ApplicationDbContext : IdentityDbContext { private readonly ICurrentUserService _currentUserService; public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ICurrentUserService currentUserService) : base(options) { _currentUserService = currentUserService; } public override int SaveChanges(bool acceptAllChangesOnSuccess) { UpdateAuditableEntities(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { UpdateAuditableEntities(); return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void UpdateAuditableEntities() { // 每次保存时实时获取当前用户名,避免缓存旧值 var currentUserName = _currentUserService.UserName; var now = DateTime.UtcNow; foreach (var entry in ChangeTracker.Entries<AuditableEntity>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedBy = currentUserName; entry.Entity.CreatedAt = now; entry.Entity.LastModifiedBy = currentUserName; entry.Entity.LastModifiedAt = now; break; case EntityState.Modified: entry.Entity.LastModifiedBy = currentUserName; entry.Entity.LastModifiedAt = now; // 禁止修改创建信息 entry.Property(x => x.CreatedBy).IsModified = false; entry.Property(x => x.CreatedAt).IsModified = false; break; } } } }
5. 非请求场景的特殊处理
如果需要在后台任务、定时任务等非请求上下文调用UnitOfWork,需要手动创建服务范围,并手动设置用户信息(如果需要审计):
using var scope = app.Services.CreateScope(); var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); // 若需要审计,可手动注入一个模拟的CurrentUserService,或者修改DbContext支持手动设置用户信息 await unitOfWork.SaveAsync();
内容的提问来源于stack exchange,提问作者Ayobamilaye
相关产品推荐
相关产品推荐

