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

ade*_*lin 17 c# entity-framework-core .net-core asp.net-core

我正在尝试开发一个类库,我想在其中实现自定义DbContext.在SaveChanges方法中DbContext,我需要获取当前用户的信息(部门,用户名等)以供审计.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();
}
Run Code Online (Sandbox Code Playgroud)

想到两个选项:

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

问题:哪种情况最适合我的情况?你有其他建议吗?

Rio*_*ams 36

我实现了与此类似的是覆盖的方法这个博客帖子,基本上涉及创建将使用依赖注入注入一个服务HttpContext(和潜在的用户信息)到一个特定的背景下,但是还是你更喜欢使用它.

一个非常基本的实现可能看起来像这样:

public class UserResolverService  
{
    private readonly IHttpContextAccessor _context;
    public UserResolverService(IHttpContextAccessor context)
    {
        _context = context;
    }

    public string GetUser()
    {
       return _context.HttpContext.User?.Identity?.Name;
    }
}
Run Code Online (Sandbox Code Playgroud)

您只需将此注入到文件中ConfigureServices方法的管道中Startup.cs:

services.AddTransient<UserResolverService>();
Run Code Online (Sandbox Code Playgroud)

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

public partial class ExampleContext : IExampleContext
{
    private YourContext _context;
    private string _user;
    public ExampleContext(YourContext context, UserResolverService userService)
    {
        _context = context;
        _user = userService.GetUser();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 现在,IHttpContextAccessor默认情况下未在服务中注册.我们必须在Startup.cs`manage.TryAddSingleton <IHttpContextAccessor,HttpContextAccessor>();中手动连接它. (11认同)
  • 返回后删除await关键字,例如`public string GetUser(){return _context.HttpContext.User?.Identity?.Name; }` (8认同)
  • 如果使用 .NET 核心 2.1,他们添加了`services.AddHttpContextAccessor()`,如果您在 [源代码](https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3dsrc/ .AspNetCore.Http/HttpServiceCollectionExtensions.cs) 它使用`services.TryAddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;()` (4认同)

Ogg*_*las 5

感谢@RionWilliams 提供原始答案。这就是我们如何解决CreatedBy和UpdatedBy通过DbContextAD B2C用户和Web API中的.Net Core3.1。SysStartTime并且SysEndTime基本上CreatedDate并且UpdatedDate但是通过时态表具有版本历史记录(关于在任何时间点存储在表中的数据的信息)。

更多关于这里:

/sf/answers/4534366091/

通用接口:

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; }
}
Run Code Online (Sandbox Code Playgroud)

数据库上下文:

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);
}
Run Code Online (Sandbox Code Playgroud)

扩展应用程序数据库上下文:

public class ExtendedApplicationDbContext
{
    public ApplicationDbContext _context;
    public UserResolverService _userService;

    public ExtendedApplicationDbContext(ApplicationDbContext context, UserResolverService userService)
    {
        _context = context;
        _userService = userService;
        _context._currentUserExternalId = _userService.GetNameIdentifier();
    }
}
Run Code Online (Sandbox Code Playgroud)

用户解析服务:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

启动:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    
    services.AddTransient<UserResolverService>();
    
    services.AddTransient<ExtendedApplicationDbContext>();
    ...
Run Code Online (Sandbox Code Playgroud)

然后可以像这样在任何地方使用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;
    }
Run Code Online (Sandbox Code Playgroud)