我已经在Visual Studio中使用模板创建了.net核心2.1 MVC应用程序,该模板具有Identity预设(用户帐户存储在应用程序中),并且我试图使某些审核字段自动化。
基本上,我想做的是重写SaveChangesAsync()方法,以便每当对实体进行更改时,当前登录的用户ID都将设置为CreatedBy或ModifiedBy属性的审核属性,这些属性是作为阴影属性创建的在实体上。
我看了看似乎有很多答案,但是令人惊讶的是,没有一个答案对我有用。我尝试注入IHttpContext,HttpContext,UserManager,或者我似乎无法访问返回用户ID的方法,或者收到循环依赖项错误,但我不太理解为什么会发生这种情况。
我真的很想这个。我认为类似这样的事情应该非常简单,但是我很难确定如何做。似乎有针对Web api控制器或MVC控制器的有据可查的解决方案,但不适用于ApplicationDbContext。
如果有人可以帮助我或者至少将我指出正确的方向,我将非常感谢,谢谢。
答案 0 :(得分:3)
我们称之为DbContextWithUserAuditing
public class DBContextWithUserAuditing : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
public string UserId { get; set; }
public int? TenantId { get; set; }
public DBContextWithUserAuditing(DbContextOptions<DBContextWithUserAuditing> options) : base(options) { }
// here we declare our db sets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.NamesToSnakeCase(); // PostgreSQL
modelBuilder.EnableSoftDelete();
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return (await base.SaveChangesAsync(true, cancellationToken));
}
}
然后,您具有请求管道以及所需的内容-是一个过滤器挂钩,您可以在其中设置用户ID
public class AppInitializerFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializerFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
您可以通过以下方式(Startup.cs文件)激活此过滤器
services
.AddMvc(options =>
{
options.Filters.Add(typeof(OnRequestInit));
})
您的应用程序随后可以自动将UserID和TenantID设置为新创建的记录
public static class ChangeTrackerExtensions
{
public static void ProcessCreation(this ChangeTracker changeTracker, string userId, int? tenantId)
{
foreach (var item in changeTracker.Entries<IHasCreationTime>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreationTime = DateTime.Now;
}
foreach (var item in changeTracker.Entries<IHasCreatorUserId>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreatorUserId = userId;
}
foreach (var item in changeTracker.Entries<IMustHaveTenant>().Where(e => e.State == EntityState.Added))
{
if (tenantId.HasValue)
{
item.Entity.TenantId = tenantId.Value;
}
}
}
我不建议在您的DbContext类中注入HttpContext
,UserManager
或其他任何东西,因为这样会违反单一责任原则。
答案 1 :(得分:1)
感谢所有答案。最后,我决定创建一个UserResolveService,该服务通过DI接收HttpContextAccessor,然后可以获取当前用户的名称。然后,使用该名称,我可以查询数据库以获取可能需要的任何信息。然后,将该服务注入到ApplicationDbContext上。
IUserResolveService.cs
public interface IUserResolveService
{
Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext);
}
UserResolveService.cs
public class UserResolveService : IUserResolveService
{
private readonly IHttpContextAccessor httpContextAccessor;
public UserResolveService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public async Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext)
{
var currentSessionUserEmail = httpContextAccessor.HttpContext.User.Identity.Name;
var user = await dbContext.Users
.SingleAsync(u => u.Email.Equals(currentSessionUserEmail));
return user.Id;
}
}
您必须在启动时注册服务,然后将其注入ApplicationDbContext,您可以像这样使用它:
ApplicationDbContext.cs
var dbContext = this;
var currentSessionUserId = await userResolveService.GetCurrentSessionUserId(dbContext);