在OnModelCreating之外添加全局过滤器

时间:2020-04-16 11:10:48

标签: asp.net-core .net-core entity-framework-core

我有一个多租户应用程序,在该应用程序中,用户成功登录后将被重定向到一个页面,在该页面上,他可以选择要与之合作的企业。

我需要添加许多依赖于企业ID的全局过滤器。但是,似乎在创建dbcontext时调用了OnModelCreating。 dbcontext是在用户想要登录时创建的,但是,由于我尚未选择要与之共事的企业,因此我目前还不知道他想与之合作的企业。因此,我无法添加过滤器。

我想在选择企业ID时添加这些过滤器。可能吗?因为我看到的所有示例都在OnModelCreating中添加了它们,但在其他地方却找不到任何参考。

这是OnModelCreating方法的示例:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //Last in wins so first set base model features
    base.OnModelCreating(modelBuilder);

    HttpContext httpContext = _httpContextAccessor.HttpContext;


    if (httpContext.Request.Headers.ContainsKey("X-TENANT-ID") == true)
    {
        string tenant = httpContext.Request.Headers["X-TENANT-ID"];
        int enterpriseId = int.Parse(tenant);

        //Define global filters for all entities
        modelBuilder.Entity<Enterprise>().HasQueryFilter(p => !p.IsDeleted);
        modelBuilder.Entity<Vehicle>().HasQueryFilter(o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
        modelBuilder.Entity<BillingInfo>().HasQueryFilter( o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
        modelBuilder.Entity<DriverAppUser>().HasQueryFilter( o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
        modelBuilder.Entity<Agency>().HasQueryFilter( o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
        modelBuilder.Entity<InsuranceCarrier>().HasQueryFilter( o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
        modelBuilder.Entity<Particular>().HasQueryFilter( o => o.EnterpriseId == enterpriseId && !o.IsDeleted);
    }
}

您将如何做?

1 个答案:

答案 0 :(得分:0)

最后我明白了。我正在使用Jeffrey Parks建议的2个DbContext和一个IModelCacheKeyFactory类来为每个租户生成一个不同的密钥。

根据租户需要运行OnModelCreating的DBContext需要定义为:

public class ApplicationDbContext : DbContext
{ 
    private readonly IHttpContextAccessor _httpContextAccessor;

    public int EnterpriseId { get; protected set;  } = -1;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
        EnterpriseId = _httpContextAccessor.HttpContext.GetCurrentEnterpriseIdNotNull();
    }

    ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //Last in wins so first set base model features
        base.OnModelCreating(modelBuilder);

        HttpContext httpContext = _httpContextAccessor.HttpContext;

        int enterpriseId = httpContext.GetCurrentEnterpriseIdNotNull();                

        //Define global filters for all entities
        modelBuilder.Entity<Vehicle>().HasQueryFilter(o => o.EnterpriseId == EnterpriseId && !o.IsDeleted);
        ...
     }
}

请注意我如何使用GetCurrentEnterpriseIdNotNull方法获取TenantId。这是一个HTTPContext扩展方法,该方法从HttpContext.User获得声明,该声明先前已由中间件插入并验证。您可以将其替换为代码以获取租户。它可以是服务,等等。

用于缓存模型的密钥生成器非常简单:

public class TenantModelCacheKeyFactory : IModelCacheKeyFactory
{
    public object Create(DbContext context) =>

        context is ApplicationDbContext dynamicContext
            ? (context.GetType(), dynamicContext.EnterpriseId)
            : (object)context.GetType();

}

注意如何从创建的DbContext(为每个HTTP请求创建的)中提取tenantId。

此后,如果您使用正确的租户ID运行请求,则为此租户调用OnModelCreating后将创建一个新的模型缓存,并且一切将顺利进行。如果另一个用户登录同一租户,则不会创建新模型,也不会提供缓存的模型(请记住,始终为每个请求创建DbContext,因为它是作为范围服务添加的)。如果另一个用户使用不同的tenantId登录,则会再次调用OnModelCreating,并且还将创建另一个模型缓存并提供服务。