我是剃刀页面/efcore/aspnet身份的新手,一直在努力解决这个问题,但它打败了我。
基本上,我使用AspNet Identity进行用户身份验证&授权。我用一个额外的OrganizationId扩展了AspNetUsers,这是一个FK到Organisation实体;并将ID作为声明添加到身份声明存储中。这很好用。
现在,我需要根据经过身份验证的用户的organizationId设置efcore全局过滤器,以便他们只能查看分配给其组织的数据。
但是,我无法在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);
}
我需要输入用户组织ID,而不是全局过滤器中的1,该ID存储为用户声明。通常我用这个得到它:
User.FindFirstValue("OrganisationId")
但是,用户在当前上下文中不存在。
所以我需要在稍后阶段应用查询过滤器,即在用户身份验证之后?有什么可以从中端/逻辑层方法开始的指针吗?
当然这是对体系结构的看法,但我把它分解成这样:
数据层—此层负责访问执行应用程序之外的资源(通常)。这包括:;数据库、文件IO、Web Api等
业务/逻辑层-此层的职责(可进一步细分)应验证、授权、验证和构建代表业务需求的对象。为了构建这些对象,它可能会使用一个或多个数据访问对象(例如,它可能使用IO DA从本地文件系统或Azure存储检索映像,并使用数据库DA检索有关该映像的元数据)。
Presentation/Exposure层-该层的职责是将对象包装并转换为消费者需要的对象(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或WebApi使用谓词可能会付出更多的努力。
其次,一些需求导致您无法使用实体框架。你可以用Dapper或其他一些新的更好的技术/框架来代替它。您只需要创建新的I<whataver>DA
类,因为消费逻辑不知道除了这些接口之外的任何东西(针对接口编程、L in SOLID编程原理和I in SOLID程序原理)。
我不会一直使用这种模式,因为对于一些较小的网站来说,这是一个太多的回报。
我建议将溶液分解为两部分
-
在dbcontext中添加一个组织id,很像多租户环境中的租户id。例如,请参阅此链接。
-
下一个挑战是将组织id作为参数传递给DbContext构造函数。为此,您可以为DbContext创建一个工厂。由于您将OrganizationId存储在声明中。工厂可以访问相同的声明HttpContext,并在实例化dbContext时将组织id作为参数传递。
这并不完美,但可以给你一个起点。