我有一个包含
TenantService
的 MultiTenant WebApp。 DbContext 配置在运行时加载,连接字符串来自另一台服务器。为简化起见,此处忽略,因为它工作正常。
每个
BaseItem
都有属性Sharedwith
,Public
,Private
,Tenant
或Archived
来自enum
;
每个 BaseItem
都有属性 TenantId
和 CreatedByUserId
,具体取决于项目的创建者。
每个 BaseItem
都有未映射的属性 canView
,它在运行时计算为 true 或 false,对于 Public
为真,如果 loggedinuser = createdbyuserid
对于 Private
则为真,如果 TenantId = loggedinuser's TenantId
则为真。
问题:
CheckifCanView
中,我如何访问ApplicationDbContext _context
以获得TenantId
和LoggedInUserId
?canView
。我不能在 modelBuilder.Entity<BaseItem>().HasQueryFilter(x => x.canView == true);
中做 OnModelCreating
因为显然我首先需要计算 canView
。
有没有其他方法可以全局过滤所有BaseItems
?。我有数百个。 public IEnumerable<TestItem1> GetTestItem1s() {
return TestItem1s.AsEnumerable().Where(x => x.canView == true); ;
}
我尝试了什么: 我之前已经在控制器的 foreach 中计算了
canView
并将 model.Where(x=>x.canView==true)
返回给视图,但这不是很有效。
我试过注入另一个用户服务,但由于数据来自同一个 DbContext 并且 DbContext 是在运行时配置的,所以这是不行的。
我试图访问 httpcontext,因为我可以从标头或 cookie 中存储和检索它们,但无法注入 httpcontext。
namespace WebApplication1
{
public interface ITenantService
{
public int GetTenantId();
public int GetLoggedInUserId();
}
public class TenantService : ITenantService
{
public int GetTenantId() { return 1; }
public int GetLoggedInUserId() { return 1; }
}
public class ApplicationDbContext : IdentityDbContext
{
public int TenantId;
public int LoggedInUserId;
private readonly ITenantService _tenantService;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenantService tenantService)
: base(options)
{
_tenantService = tenantService;
TenantId = _tenantService.GetTenantId();
LoggedInUserId = _tenantService.GetLoggedInUserId();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//CANT FILTER BEFORE CANVIEW VALUE SET
//modelBuilder.Entity<BaseItem>().HasQueryFilter(x => x.canView == true);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) {
foreach (var entry in ChangeTracker.Entries<hasTenant>().ToList())
{
switch (entry.State)
{
case EntityState.Added:
//entry.Entity.TenantId = TenantId;
break;
}
}
foreach (var entry in ChangeTracker.Entries<hasUser>().ToList())
{
switch (entry.State)
{
case EntityState.Added:
//entry.Entity.CreatedbyUserId = TenantId;
break;
}
}
var result = await base.SaveChangesAsync(cancellationToken);
return result;
}
public DbSet<TestItem1> TestItem1s { get; set; } = default;
public DbSet<TestItem2> TestItem2s { get; set; } = default;
//REFER TO QUESTION 2. Apply global filter for canView==true but only after it has been calculated.
public IEnumerable<TestItem1> GetTestItem1s() {
return TestItem1s.AsEnumerable().Where(x => x.canView == true); ;
}
public IEnumerable<TestItem2> GetTestItem2s()
{
return TestItem2s.AsEnumerable().Where(x => x.canView == true); ;
}
}
public enum Sharedwith
{
Public,
Private,
Tenant,
Archive
}
public interface hasTenant
{
public int TenantId { get; set; }
}
public interface hasUser
{
public int CreatedbyUserId { get; set; }
}
public class BaseItem : hasTenant, hasUser
{
public int Id { get; set; }
public int TenantId { get; set; }
public string Description { get; set; }
public int CreatedbyUserId { get; set; }
public Sharedwith Sharedwith { get; set; }
[NotMapped]
public bool canView
{
get
{
return Extentions.CheckifCanView(this.TenantId, this.CreatedbyUserId, this.Sharedwith);
}
set { }
}
}
public class TestItem1 : BaseItem, hasTenant, hasUser
{
public string CustomProp { get; set; }
}
public class TestItem2 : BaseItem, hasTenant, hasUser
{
public string CustomProp { get; set; }
}
public static class Extentions
{
//CANT USE THIS IN STATIC CLASS
//readonly ApplicationDbContext _context;
//public Extentions(ApplicationDbContext _context) {
// _context = _context;
//}
public static bool CheckifCanView(int TenantId, int CreatedbyUserId, Sharedwith Sharedwith)
{
//CANT USE THIS IN STATIC CLASS
//var _context = new ApplicationDbContext();
//var c = _context.TenantId;
//REFER TO QUESTION 1. This must come from _context.
var contextTenantid = 1; //var contextTenantid = _context.TenantId;
var contextUsertid = 1; //var contextUsertid = _context.LoggedInUserId;
switch (Sharedwith)
{
case Sharedwith.Public: return true; break;
case Sharedwith.Private: return CreatedbyUserId==contextUsertid; break;
case Sharedwith.Tenant: return TenantId==contextTenantid; break;
case Sharedwith.Archive: return false; break;
default: return false;
}
}
}
}
这里是 Git 源代码
我最终没有将上下文包含到模型中,而是创建了一个扩展来进行计算。不知道它是否最充分或者是否可以有所改进,但就目前而言,它正在运行。希望它也可以帮助别人。
如果有人有更多想法,请告诉我。
首先在模型中,设置一个普通的
canView
属性:
[NotMapped]
public bool canView { get; set; }
换班
Extentions
接受ApplicationDbContext _context
public static class Extentions
{
public static bool CheckifCanView(int TenantId, int CreatedbyUserId, Sharedwith Sharedwith, ApplicationDbContext _context)
{
var contextTenantid = _context.TenantId;
var contextUsertid = _context.LoggedInUserId;
switch (Sharedwith)
{
case Sharedwith.Public:
return true;
break;
case Sharedwith.Private:
return CreatedbyUserId == contextUsertid;
break;
case Sharedwith.Tenant:
return TenantId == contextTenantid;
break;
case Sharedwith.Archive:
return false;
break;
default:
return false;
}
}
}
然后我添加了一个新的
DbSetExtensions
类:
public static class DbSetExtensions
{
public static IQueryable < T > canView < T > (this IQueryable < T > t, ApplicationDbContext _context) where T: BaseItem
{
var newt = new List < T > ();
foreach(var item in t)
{
item.canView = Extentions.CheckifCanView(item.TenantId, item.CreatedbyUserId, item.Sharedwith, _context);
if (item.canView == true) newt.Add(item);
}
return newt.AsQueryable();
}
}
现在我可以通过我的控制器执行以下操作:
public IActionResult Index()
{
var model = _context.TestItem1s.canView(_context);
return View(model);
}
这样我可以有多个
BaseItem
,我可以根据上下文的登录用户计算canView
属性,它只返回用户有权访问的属性,只需添加.canView(_context)