Blazor中如何通过依赖注入在EF Core中获取ClaimsPrincipal?
Blazor中如何通过依赖注入在EF Core中获取ClaimsPrincipal?
我太懂你这种困扰了——明明想在EF Core层面自动搞定审计字段的填充,结果把AuthenticationStateProvider直接注入DbContext后就报错,只能退而求其次在Blazor组件里手动设置,确实有点烦。其实核心问题在于DbContext的生命周期和Blazor的认证上下文不同步,直接注入会导致在认证状态还没初始化的时候就去拿用户信息,自然会出错。
给你分享一个靠谱的解决方案,既符合分层架构,又能自动填充审计字段:
第一步:创建一个专门的用户上下文服务
不要直接在DbContext里用AuthenticationStateProvider,咱们先封装一个专门的服务来获取当前用户信息,这样能更好地处理生命周期问题。
首先定义一个接口:
public interface IUserContext { Task<ClaimsPrincipal> GetCurrentUserAsync(); }
然后实现这个接口:
public class UserContext : IUserContext { private readonly AuthenticationStateProvider _authStateProvider; private ClaimsPrincipal _cachedUser; public UserContext(AuthenticationStateProvider authStateProvider) { _authStateProvider = authStateProvider; } public async Task<ClaimsPrincipal> GetCurrentUserAsync() { // 缓存用户信息,避免重复调用认证服务 if (_cachedUser != null) return _cachedUser; var authState = await _authStateProvider.GetAuthenticationStateAsync(); _cachedUser = authState.User; return _cachedUser; } }
记得在Program.cs里注册这个服务:
builder.Services.AddScoped<IUserContext, UserContext>();
第二步:让实体实现审计接口
先定义一个审计接口,统一规范需要填充的字段:
public interface IAuditable { string CreatedBy { get; set; } string LastModifiedBy { get; set; } // 要是需要时间字段的话,也可以加上CreatedDate、LastModifiedDate }
然后让你的实体都实现这个接口:
public class Product : IAuditable { public int Id { get; set; } public string Name { get; set; } // 其他业务属性... public string CreatedBy { get; set; } public string LastModifiedBy { get; set; } }
第三步:在DbContext中自动填充审计字段
现在把IUserContext注入到DbContext里,重写SaveChangesAsync方法,在保存数据前自动处理审计字段:
public class AppDbContext : DbContext { private readonly IUserContext _userContext; public AppDbContext(DbContextOptions<AppDbContext> options, IUserContext userContext) : base(options) { _userContext = userContext; } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { // 获取当前用户的标识(这里用NameIdentifier,你可以换成自己需要的Claim类型) var currentUser = await _userContext.GetCurrentUserAsync(); var userId = currentUser.FindFirstValue(ClaimTypes.NameIdentifier); // 处理新增实体:同时设置CreatedBy和LastModifiedBy foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added)) { if (entry.Entity is IAuditable auditable) { auditable.CreatedBy = userId; auditable.LastModifiedBy = userId; } } // 处理修改实体:只更新LastModifiedBy,不要覆盖CreatedBy foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) { if (entry.Entity is IAuditable auditable) { auditable.LastModifiedBy = userId; } } return await base.SaveChangesAsync(cancellationToken); } }
为什么这个方案靠谱?
IUserContext是Scoped生命周期,和Blazor组件的上下文一致,能保证在调用SaveChangesAsync时,用户已经完成认证,AuthenticationState已经初始化完成,不会出现之前的报错。- 把审计逻辑统一放在EF Core层面,符合关注点分离,不用在每个Blazor组件里重复写手动设置字段的代码。
- 还能灵活处理特殊情况,比如后台任务触发的保存操作,这时候可以在
GetCurrentUserAsync里判断是否是匿名用户,设置默认值或者跳过填充。
这样一来,你就不用再手动在Blazor层设置审计字段了,EF Core会自动帮你搞定,省心很多~
备注:内容来源于stack exchange,提问作者Jean Baumflek
相关产品推荐
相关产品推荐

