【问题标题】:Adding global filters outside OnModelCreating在 OnModelCreating 之外添加全局过滤器
【发布时间】:2020-04-20 01:20:50
【问题描述】:

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

我需要添加很多依赖于企业 ID 的全局过滤器。但似乎 OnModelCreating 在创建 dbcontext 时被调用。 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);
    }
}

你会怎么做?

【问题讨论】:

  • 我们最近在我们的应用程序中使用了几乎相同的问题和场景,用户可以选择他的租户。如果您的 Context 在用户加载第二页(需要过滤器的页面)时设置为瞬态或作用域,则应创建一个新的上下文,该上下文将再次通过此 OnModelCreating 运行。此外,由于您的 X-TENANT-ID 在 Http 标头中,请确保您已验证用户没有恶意发送他们实际上无权访问的租户 ID。
  • @JeffreyParks 我使用扩展方法 services.AddDbContext 添加了 DbContext,并且可以向您保证 OnModelCreating 只是在第一次向 WebApi 发出请求时被调用。我一直在检查来源,并且此扩展方法将服务注册为范围。那么,它对您有何帮助? :/。瞬态不是一种选择。
  • 我想我需要多睡一会儿。您声明要创建一个新的 dbContext。您是否介意添加一些代码来查看如何创建这个新的 dbcontext 以及如何添加到依赖项注入系统中?我真的很感激。
  • 当请求到达服务器时,它会创建 Controller/Service/DbContext 的新实例(不知道你有什么层次结构,但它是相同的概念)。这意味着每个 HTTP 请求都会获得一个新的 DbContext,这意味着如果所有内容都设置为 Transient 和/或 Scoped,您的代码应该可以正常工作。如果以后有更多时间,我可以发布一个完整的示例作为答案。
  • 只是补充一点信息。实体框架有一个缓存系统。该文档明确指出,克服它可能会导致性能问题。我开始考虑不使用全局过滤器,并使用一些辅助方法来添加常见的过滤器,如租户和软删除。无论如何,我将非常感谢您如何建立您的解决方案。谢谢!

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


【解决方案1】:

终于明白了。我正在使用 Jeffrey Parks 建议的 2 个 DbContexts 和一个 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 并创建另一个模型缓存并提供服务。

【讨论】:

    猜你喜欢
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    • 2014-09-20
    • 2022-01-05
    • 2018-05-12
    • 1970-01-01
    相关资源
    最近更新 更多