如何使用 Net Core 在 DbContext 中获取用户信息

Posted

技术标签:

【中文标题】如何使用 Net Core 在 DbContext 中获取用户信息【英文标题】:How to get user information in DbContext using Net Core 【发布时间】:2016-07-23 21:34:00 【问题描述】:

我正在尝试开发一个类库,我想在其中实现自定义DbContext。在DbContextSaveChanges 方法中,我需要获取当前用户的信息(部门、用户名等)以进行审计。 DbContext 代码的一部分如下:

public override int SaveChanges()

    // find all changed entities which is ICreateAuditedEntity 
    var addedAuditedEntities = ChangeTracker.Entries<ICreateAuditedEntity>()
           .Where(p => p.State == EntityState.Added)
           .Select(p => p.Entity);

    var now = DateTime.Now;

    foreach (var added in addedAuditedEntities)
    
        added.CreatedAt = now;
        added.CreatedBy = ?;
        added.CreatedByDepartment = ?
    
    return base.SaveChanges();

想到两个选项:

使用HttpContext.Items保存用户信息,注入IHttpContextAccessor并从 HttpContext.Items(在这种情况下DbContext取决于HttpContext,是吗 对吗?) 使用 ThreadStatic 对象而不是 HttpContext.Items 并从对象中获取信息(我阅读了一些帖子 ThreadStatic 不安全)

问题:哪个最适合我的情况?您还有其他建议吗?

【问题讨论】:

与其直接在你的 DbContext 中依赖 IHttpContextAccessor 为什么不创建一个像 AuditLogger 这样的服务类并让你的 DbContext 依赖它,AuditLogger 可以依赖 IHttpContextAccessor 作为它自己的内部实现细节 这似乎是一个很好的方法,我会尝试实现它。谢谢。 【参考方案1】:

我实现了一种类似于this blog post 中介绍的方法,基本上涉及创建一个服务,该服务将使用依赖注入将HttpContext(和底层用户信息)注入特定上下文,或者您更喜欢使用它。

一个非常基本的实现可能如下所示:

public class UserResolverService  

    private readonly IHttpContextAccessor _context;
    public UserResolverService(IHttpContextAccessor context)
    
        _context = context;
    

    public string GetUser()
    
       return _context.HttpContext.User?.Identity?.Name;
    

您只需要在Startup.cs 文件中的ConfigureServices 方法中将其注入管道:

services.AddTransient<UserResolverService>();

最后,只需在您指定的 DbContext 的构造函数中访问它:

public partial class ExampleContext : IExampleContext

    private YourContext _context;
    private string _user;
    public ExampleContext(YourContext context, UserResolverService userService)
    
        _context = context;
        _user = userService.GetUser();
    

然后您应该能够使用_user 在您的上下文中引用当前用户。这可以很容易地扩展到存储/访问当前请求中可用的任何内容。

【讨论】:

return后去掉await关键字,像这样public string GetUser() return _context.HttpContext.User?.Identity?.Name; 现在 IHttpContextAccessor 默认没有在服务中注册。我们必须在 Startup.cs services.TryAddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); 中手动连接它 非常优雅的解决方案,谢谢。我正在考虑添加一个由 UserResolverService 实现的接口,并使 DbContext 使用它而不是 UserResloverService 本身。因此,它会将 deDbContext 与 IHttpContextAccessor 解耦,以帮助单元测试和其他用途。 如果使用 .NET core 2.1,他们添加了 services.AddHttpContextAccessor(),如果您查看 source code 的底层,它使用 services.TryAddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;() 服务引用 data\dbcontext 项目。要将服务注入数据项目,您必须引用它。恐怕会造成循环引用【参考方案2】:

感谢@RionWilliams 的原始答案。这就是我们在.Net Core 3.1 中通过DbContextAD B2C 用户和Web Api 解决CreatedBy 和UpdatedBy 的方法。 SysStartTimeSysEndTime 基本上是 CreatedDateUpdatedDate,但通过临时表具有版本历史记录(有关任何时间点存储在表中的数据的信息)。

更多关于这里:

https://***.com/a/64776658/3850405

通用接口:

public interface IEntity

    public DateTime SysStartTime  get; set; 

    public DateTime SysEndTime  get; set; 
    
    public int CreatedById  get; set; 
    
    public User CreatedBy  get; set; 

    public int UpdatedById  get; set; 

    public User UpdatedBy  get; set; 

数据库上下文:

public class ApplicationDbContext : DbContext

    public ApplicationDbContext(
        DbContextOptions options) : base(options)
    
    
    
    public DbSet<User> User  get; set; 

    public string _currentUserExternalId;
    
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    
        var user = await User.SingleAsync(x => x.ExternalId == _currentUserExternalId);

        AddCreatedByOrUpdatedBy(user);

        return (await base.SaveChangesAsync(true, cancellationToken));
    

    public override int SaveChanges()
    
        var user = User.Single(x => x.ExternalId == _currentUserExternalId);

        AddCreatedByOrUpdatedBy(user);

        return base.SaveChanges();
    

    public void AddCreatedByOrUpdatedBy(User user)
    
        foreach (var changedEntity in ChangeTracker.Entries())
        
            if (changedEntity.Entity is IEntity entity)
            
                switch (changedEntity.State)
                
                    case EntityState.Added:
                        entity.CreatedBy = user;
                        entity.UpdatedBy = user;
                        break;
                    case EntityState.Modified:
                        Entry(entity).Reference(x => x.CreatedBy).IsModified = false;
                        entity.UpdatedBy = user;
                        break;
                
            
        
    
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        foreach (var property in modelBuilder.Model.GetEntityTypes()
            .SelectMany(t => t.GetProperties())
            .Where(p => p.ClrType == typeof(string)))
        
            if (property.GetMaxLength() == null)
                property.SetMaxLength(256);
        

        foreach (var property in modelBuilder.Model.GetEntityTypes()
            .SelectMany(t => t.GetProperties())
            .Where(p => p.ClrType == typeof(DateTime)))
        
            property.SetColumnType("datetime2(0)");
        

        foreach (var et in modelBuilder.Model.GetEntityTypes())
        
            foreach (var prop in et.GetProperties())
            
                if (prop.Name == "SysStartTime" || prop.Name == "SysEndTime")
                
                    prop.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAddOrUpdate;
                
            
        

        modelBuilder.Entity<Question>()
            .HasOne(q => q.UpdatedBy)
            .WithMany()
            .OnDelete(DeleteBehavior.Restrict);

ExtendedApplicationDbContext:

public class ExtendedApplicationDbContext

    public ApplicationDbContext _context;
    public UserResolverService _userService;

    public ExtendedApplicationDbContext(ApplicationDbContext context, UserResolverService userService)
    
        _context = context;
        _userService = userService;
        _context._currentUserExternalId = _userService.GetNameIdentifier();
    

用户解析服务:

public class UserResolverService

    public readonly IHttpContextAccessor _context;

    public UserResolverService(IHttpContextAccessor context)
    
        _context = context;
    

    public string GetGivenName()
    
        return _context.HttpContext.User.FindFirst(ClaimTypes.GivenName).Value;
    

    public string GetSurname()
    
        return _context.HttpContext.User.FindFirst(ClaimTypes.Surname).Value;
    

    public string GetNameIdentifier()
    
        return _context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
    

    public string GetEmails()
    
        return _context.HttpContext.User.FindFirst("emails").Value;
    

启动:

public void ConfigureServices(IServiceCollection services)

    services.AddHttpContextAccessor();
    
    services.AddTransient<UserResolverService>();
    
    services.AddTransient<ExtendedApplicationDbContext>();
    ...

然后可以像这样在任何Controller中使用:

public class QuestionsController : ControllerBase

    private readonly ILogger<QuestionsController> _logger;
    private readonly ExtendedApplicationDbContext _extendedApplicationDbContext;

    public QuestionsController(ILogger<QuestionsController> logger, ExtendedApplicationDbContext extendedApplicationDbContext)
    
        _logger = logger;
        _extendedApplicationDbContext = extendedApplicationDbContext;
    

【讨论】:

以上是关于如何使用 Net Core 在 DbContext 中获取用户信息的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?

ASP.NET Core DbContext 注入

ASP.NET Core Web API - 如何在中间件管道中隐藏 DbContext 事务?

如何在 EF Core 中实例化 DbContext

在 ASP.NET Core 中使用 DbContext 注入并行 EF Core 查询

将 DbContext 注入 LoggerProvider 会在 .NET Core 中引发 ***Exception