DbContext 模型构建器中的用户详细信息

User details within the DbContext model builder

我是 razor pages/efcore/aspnet identity 的新手,一直在努力解决这个问题,但它打败了我。

基本上,我使用 AspNet Identity 进行用户身份验证和授权。我用一个额外的 OrganisationId 扩展了 AspNetUsers,它是组织实体的 FK;并将 ID 作为声明添加到身份声明存储中。这很好用。

现在我需要根据经过身份验证的用户的 organizationId 设置 efcore global filter,这样他们就只能查看分配给他们组织的数据。

但是,我无法在 ModelBuilder 中访问经过身份验证的用户详细信息。

public class SDMOxContext : IdentityDbContext<
        ApplicationUser, ApplicationRole, string,
        ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin,
        ApplicationRoleClaim, ApplicationUserToken>
    {

        public SDMOxContext(DbContextOptions<SDMOxContext> options)
            : base(options)
        { }

        protected override void OnModelCreating(ModelBuilder builder)
        {

            base.OnModelCreating(builder);
        // Set global filter so users can only see projects within their organisation.
        builder.Entity<Project>().HasQueryFilter(project => project.OrganisationId == 1);

    }

我需要输入用户 organizationid,而不是全局过滤器中的 1,它存储为用户声明。通常我是这样理解的:

User.FindFirstValue("OrganisationId")

但是,用户在当前上下文中不存在。

我建议将解决方案分解成两部分

  1. 在您的 dbcontext 中添加组织 ID,就像多租户环境中的租户 ID 一样。例如,参见此 link

  2. 下一个挑战是将组织 ID 作为参数传递给 DbContext 构造函数。为此,您可以为 DbContext 创建一个工厂。由于您将 OrganizationId 存储在声明中。工厂可以访问相同的声明 HttpContext 并在实例化 dbContext 时将组织 ID 作为参数传递。

它并不完美,但可以给你一个起点。

So I would need to apply the query filter at a later stage, ie. after user authentication? Any pointers where to start with a mid-tier/logic-tier approach?

虽然这是对架构的看法,但我将其分解如下:

数据层——该层负责访问正在执行的应用程序之外的资源(通常)。这包括;数据库、文件 IO、Web Api 等

Business/Logic-Tier - 这一层的职责(可以进一步细分)应该验证、授权、验证和构建代表业务需求的 对象。为了构建这些对象,它可能会使用一个或多个数据访问对象(例如,它可能会使用 IO DA 从本地文件系统或 Azure 存储中检索图像,并使用数据库 DA 来检索有关该图像的元数据)。

Presentation/Exposure-Tier - 这一层的职责是将对象包装并转换为消费者需要的(winforms、wpf、html、json、xml、二进制序列化等)。

通过将逻辑排除在数据层之外(即使在多租户系统中),您可以获得跨所有系统访问数据的能力(相信我,这里可以赚很多钱)。

这可能比我在这么短的地方所能解释的要多得多,而且非常我的意见。我将 略去一些内容 但现在开始了。

数据层

namespace ProjectsData
{
  public interface IProjectDA 
  { 
    IProjectDO GetProject(Guid projectId, Guid organizationId);
  }
  private class ProjectDA : DbContext, IProjectDA
  {
    public ProjectDA (...)
    public IEnumerable<ProjectDO> Projects { get; set; }
    protected override void OnModelCreating(ModelBuilder builder) {... }
    public IProjectDO GetProject(Guid projectId, Guid organizationId)
    {
      var result = Projects
        .FirstOrDefault(p => p.Id == projectId && OrganizationId = organizationId);
      return result;
    }
  }

  public interface IProjectDO{ ... }
  private class ProjectDO: IProjectDO
  {
    public Guid Id { get; set; }
    public Guid OrganizationId { get; set; }
    public Guid CategoryId { get; set; }
  }
}

逻辑

namespace ProjectBusiness
{
  public interface IProjectBO { .. }
  public interface IOrganization 
  { 
    Guid OrganizationId { get; }
  }
  private class ProjectBA : IProjectBO
  {
    private readonly IProjectDA _projectDA;
    private readonly IIdentity _identity;
    private readonly IOrganization _organization;
    public  ProjectLogic(IProjectDA projectDA, 
      IIdentity identity,
      IOrganizationContext organizationContext)
    {
      _projectDA = projectDA;
      _identity = identity;
    }
    public IProjectBO GetProject(Guid id)
    {
      var do = _projectDA
        .GetProject(id, _organization);

      var result = map.To<ProjectBO>(do);

      return result;
    }
  }
  public interface IProjectBO { .. }
  private class ProjectBO 
  { 
    public Guid Id { get; set; }
    public Guid OrganizationId { get; set; }
    public Guid CategoryId { get; set; }
  }
}

因此在这些情况下,数据层知道请求的类型,但多租户不了解。它不限制基于任何东西的所有请求。这种 架构 在很多方面都有优势。

首先,在上面的示例中,您的产品起飞,您的主管想知道哪些类别最受欢迎。

namespace StatisticsBusiness
{
  public interface IStatisticsBO 
  {
    IEnumerable<ICategoryStatisticBO> CategoryStatistics { get; set; }
  }
  public interface ICategoryStaticBO
  {
    Guid CategoryId { get; }
    int ProjectCount { get; }
  }
  private class StatisticsBA : IStatisticsBO
  {
    private readonly IProjectDA _projectDA;
    private readonly IIdentity _identity;
    public  ProjectLogic(IProjectDA projectDA, 
      IIdentity identity)
    {
      _projectDA = projectDA;
      _identity = identity;
    }

    public IEnumerable<IProjectBO GetOrderedCategoryPopularity()
    {
      var dos = _projectDA
        .GetProjectCategoryCounts()

      var result = map.To<IEnumerable<IStatisticsBO>>(dos);

      return result;
    }
  }
  public interface IStatisticsBO{ .. }
  private class StatisticsBO 
  { 
    public Guid CategoryId { get; }
    public int ProjectCount { get; }
  }
}

注意:有些人更喜欢将表达式作为谓词传递。两者都有其优点和缺点。如果您决定走谓词路线,那么您将必须决定是否所有数据访问类型都使用谓词。只需意识到对 IO 或 Web Api 使用谓词可能会付出更多的努力,这是值得的。

其次,某些要求导致您无法使用Entity Framework。您可以将其替换为 Dapper 或其他更好的新产品 technology/framework。您只需要创建新的 I<whataver>DA 类,因为使用逻辑不知道除这些接口 (programming against an interface, the L in SOLID programming principles and the I in SOLID programming principles) 之外的任何其他内容。

我不会一直使用这种模式,因为对于一些较小的网站来说,它的工作量太大了。