如何使用Net Core获取DbContext中的用户信息
How to get user information in DbContext using Net Core
我正在尝试开发一个 class 库,我想在其中实现自定义 DbContext
。在DbContext
的SaveChanges
方法中,我需要获取当前用户的信息(部门、用户名等)用于审计目的。 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 不安全)
问题:哪个最适合我的情况?您还有其他建议吗?
我实现了一种类似于 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
在您的上下文中引用当前用户。这可以很容易地扩展为存储/访问当前请求中可用的任何内容。
感谢@RionWilliams 提供的原始答案。这就是我们在 .Net Core
3.1 中通过 DbContext
、AD B2C
用户和 Web Api 解决 CreatedBy 和 UpdatedBy 的方法。 SysStartTime
和 SysEndTime
基本上是 CreatedDate
和 UpdatedDate
但具有版本历史记录(有关在任何时间点存储在 table 中的数据的信息)通过时间 tables.
更多信息请点击此处:
通用接口:
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;
}
我正在尝试开发一个 class 库,我想在其中实现自定义 DbContext
。在DbContext
的SaveChanges
方法中,我需要获取当前用户的信息(部门、用户名等)用于审计目的。 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 不安全)
问题:哪个最适合我的情况?您还有其他建议吗?
我实现了一种类似于 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
在您的上下文中引用当前用户。这可以很容易地扩展为存储/访问当前请求中可用的任何内容。
感谢@RionWilliams 提供的原始答案。这就是我们在 .Net Core
3.1 中通过 DbContext
、AD B2C
用户和 Web Api 解决 CreatedBy 和 UpdatedBy 的方法。 SysStartTime
和 SysEndTime
基本上是 CreatedDate
和 UpdatedDate
但具有版本历史记录(有关在任何时间点存储在 table 中的数据的信息)通过时间 tables.
更多信息请点击此处:
通用接口:
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;
}