访问模型中的 DbContext 以设置属性并为多租户 webapp 应用全局过滤器

问题描述 投票:0回答:1

我有一个包含

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
则为真。

问题:

  1. 在我的
    CheckifCanView
    中,我如何访问
    ApplicationDbContext _context
    以获得
    TenantId 
    LoggedInUserId
  2. 在 DbCOntext 中,我为每种类型的 BaseItem 过滤
    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 源代码

c# entity-framework asp.net-core model-view-controller dbcontext
1个回答
0
投票

我最终没有将上下文包含到模型中,而是创建了一个扩展来进行计算。不知道它是否最充分或者是否可以有所改进,但就目前而言,它正在运行。希望它也可以帮助别人。

如果有人有更多想法,请告诉我。

首先在模型中,设置一个普通的

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)

© www.soinside.com 2019 - 2024. All rights reserved.