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

时间:2018-10-19 22:55:28

标签: asp.net-core .net-core asp.net-identity asp.net-core-2.0

我已经在Visual Studio中使用模板创建了.net核心2.1 MVC应用程序,该模板具有Identity预设(用户帐户存储在应用程序中),并且我试图使某些审核字段自动化。

基本上,我想做的是重写SaveChangesAsync()方法,以便每当对实体进行更改时,当前登录的用户ID都将设置为CreatedBy或ModifiedBy属性的审核属性,这些属性是作为阴影属性创建的在实体上。

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

我真的很想这个。我认为类似这样的事情应该非常简单,但是我很难确定如何做。似乎有针对Web api控制器或MVC控制器的有据可查的解决方案,但不适用于ApplicationDbContext。

如果有人可以帮助我或者至少将我指出正确的方向,我将非常感谢,谢谢。

2 个答案:

答案 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类中注入HttpContextUserManager或其他任何东西,因为这样会违反单一责任原则

答案 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);
相关问题