如何使用 Identity 在 ApplicationDbContext 中获取当前登录的用户 ID?

Joã*_*iva 4 asp.net-identity .net-core asp.net-core asp.net-core-2.0

我已经使用 Visual Studio 中的模板和 Identity 预设(存储在应用程序中的用户帐户)创建了一个 .net core 2.1 MVC 应用程序,并且我正在尝试自动化一些审计字段。

基本上我想要做的是覆盖 SaveChangesAsync() 方法,以便每当对实体进行更改时,当前登录的用户 ID 都会设置为 CreatedBy 或 ModifiedBy 属性的审计属性,这些属性被创建为实体的影子属性.

我看过似乎有很多答案,但令人惊讶的是,没有一个对我有用。我曾尝试注入 IHttpContext、HttpContext、UserManager,但我似乎无法访问返回用户 ID 的方法,或者出现循环依赖错误,我不太明白为什么会发生这种情况。

我真的对这个感到绝望。我认为这样的事情应该很简单,但我真的很难弄清楚如何去做。似乎有针对 web api 控制器或 MVC 控制器的有据可查的解决方案,但不能在 ApplicationDbContext 中使用。

如果有人可以帮助我或至少为我指明正确的方向,我将不胜感激,谢谢。

Ale*_*man 9

让我们称之为 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你有请求管道和你需要的 - 是一个过滤器钩子,你可以在其中设置你的用户 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过以下方式激活此过滤器(Startup.cs 文件)

services
    .AddMvc(options =>
    {
        options.Filters.Add(typeof(OnRequestInit));
    })
Run Code Online (Sandbox Code Playgroud)

然后,您的应用程序能够自动将 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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我不建议将HttpContext,UserManager或任何东西注入你的 DbContext 类,因为这样你就违反了单一职责原则


Joã*_*iva 3

感谢所有的答案。最后,我决定创建一个 UserResolveService,它通过 DI 接收 HttpContextAccessor,然后获取当前用户的名称。通过该名称,我可以查询数据库以获取我可能需要的任何信息。然后我将此服务注入到 ApplicationDbContext 上。

IUserResolveService.cs

public interface IUserResolveService
{
    Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext);
}
Run Code Online (Sandbox Code Playgroud)

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

您必须在启动时注册该服务并将其注入到 ApplicationDbContext 中,然后您可以像这样使用它:

应用程序DbContext.cs

var dbContext = this;

var currentSessionUserId = await userResolveService.GetCurrentSessionUserId(dbContext);
Run Code Online (Sandbox Code Playgroud)