Asp.Net Core 5中无需从Controller传参,如何在审计扩展中获取AppUser
方案1:通过服务定位器在扩展方法内获取AppUser(不推荐)
静态扩展方法本身没有直接访问DI容器的能力,硬要实现可以用服务定位器模式:
- 首先在
Startup.cs/Program.cs中注册HttpContext访问服务:
services.AddHttpContextAccessor();
- 定义全局静态服务提供者,启动时将容器赋值给该变量:
// 放在公共静态类中 public static class GlobalServiceProvider { public static IServiceProvider Instance { get; set; } } // Startup的Configure方法中赋值 public void Configure(IApplicationBuilder app) { GlobalServiceProvider.Instance = app.ApplicationServices; // 其他启动逻辑 }
- 改造扩展方法直接获取当前用户:
public static void WriteCreationAudit(this IAuditProperties obj) { var httpAccessor = GlobalServiceProvider.Instance.GetRequiredService<IHttpContextAccessor>(); var userManager = GlobalServiceProvider.Instance.GetRequiredService<UserManager<AppUser>>(); var userId = httpAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); var currentUser = userId == null ? null : userManager.FindByIdAsync(userId).GetAwaiter().GetResult(); obj.CreatedBy = currentUser?.FullName ?? "System"; obj.CreatedOn = DateTime.Now.InMorocco(); obj.IsEdited = false; }
该方案缺点明显:违反依赖注入的显式依赖原则,可测试性差,非HTTP场景(如后台任务)下HttpContext为空会触发异常,仅做参考不建议生产使用。
方案2:重写EF Core的SaveChanges方法(最推荐)
这是ASP.NET Core中实现实体审计的标准方案,完全不需要Controller层参与,审计逻辑在数据持久化层自动处理,对业务代码透明。
实现步骤:
- 给自定义DbContext注入依赖:
public class AppDbContext : IdentityDbContext<AppUser> { private readonly IHttpContextAccessor _httpAccessor; private readonly UserManager<AppUser> _userManager; public AppDbContext(DbContextOptions<AppDbContext> options, IHttpContextAccessor httpAccessor, UserManager<AppUser> userManager) : base(options) { _httpAccessor = httpAccessor; _userManager = userManager; } }
- 重写SaveChanges系列方法,自动处理审计字段赋值:
public override int SaveChanges() { ProcessAuditProperties(); return base.SaveChanges(); } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { ProcessAuditProperties(); return await base.SaveChangesAsync(cancellationToken); } private void ProcessAuditProperties() { var userId = _httpAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); AppUser currentUser = null; if (userId != null) { currentUser = _userManager.FindByIdAsync(userId).GetAwaiter().GetResult(); } // 过滤所有新增/修改的审计实体 var auditEntries = ChangeTracker.Entries<IAuditProperties>() .Where(e => e.State is EntityState.Added or EntityState.Modified); foreach (var entry in auditEntries) { var entity = entry.Entity; if (entry.State == EntityState.Added) { entity.CreatedBy = currentUser?.FullName ?? "System"; entity.CreatedOn = DateTime.Now.InMorocco(); entity.IsEdited = false; } else { entity.IsEdited = true; entity.LastEditor = currentUser?.FullName ?? "System"; entity.LastEditDate = DateTime.Now.InMorocco(); // 可选:强制禁止修改创建信息字段 entry.Property(x => x.CreatedBy).IsModified = false; entry.Property(x => x.CreatedOn).IsModified = false; } } }
- 确保注册了IHttpContextAccessor服务:
services.AddHttpContextAccessor();
优化提示:可以在用户登录时将FullName写入Claims,无需每次查询UserManager,直接从HttpContext.User的Claims中读取即可,减少数据库请求。
该方案优势:
- 完全解耦审计逻辑和业务逻辑,不需要在Controller或业务层做任何审计相关操作
- 遵循依赖注入原则,可测试性好
- 支持非HTTP场景,后台任务执行时可以自定义默认系统用户信息
- 避免遗漏审计字段赋值,所有实体变更都会自动触发审计逻辑
内容的提问来源于stack exchange,提问作者JOCKH
相关产品推荐
相关产品推荐

