7

我正在编写一个多租户应用程序,每个租户模型使用一个数据库。我已允许每个用户帐户访问多个租户(只要该租户授予他们访问权限)

发送到浏览器的每个页面都包含 Site.Master 中的当前 TenantId

<%= Html.Hidden("TenantId") %>

但是,当浏览器发出任何请求(提交按钮、AJAX GET 或 AJAX POST)时,实际上不会检查此 TenantId 是否与用户当前的 TenantId 匹配。

现在,如果用户打开一个 TenantId = 1 的选项卡,然后连接到另一个 TenantId = 2 的选项卡中的另一个租户,然后切换回第一个选项卡,它就可以访问来自租户 2 的数据。

我能做些什么来解决这个问题?我有大量现有的 ActionResult 和 JsonResult 方法,我不想遍历它们中的每一个并添加

if (request.TenantId != user.CurrentTenantId) return false

因为那将是大量的重复工作

我可以更改我的基本控制器以始终读取 TenantId 的值吗?这可能适用于提交的请求(ActionResult),但是 AJAX 请求呢?

如何在不更改每个现有 AJAX 方法(有很多)的情况下检查 JsonResult 操作中页面的 TenantId?

4

4 回答 4

2

您可以检查 Global.asax.cs 文件中的 Application_Request 事件。如果您需要通过 MVC 模型绑定填充,那么可以编写一个自定义 ActionFilter 来检查它并通过 GlobalFilter 将其注册到所有操作中。

于 2013-04-15T23:42:41.997 回答
2

如果我理解正确,用户可以同时打开 2 个不同的选项卡,每个选项卡都有不同的租户。每个页面都应该显示与每个租户相关的数据。

因此,这意味着需要丢弃涉及 cookie 或会话的解决方案,因为租户特定于每个浏览器选项卡。

并阅读您对 Cyril Gupta 建议的回答,我了解每个页面上隐藏的tenantId 可能不会在每个 AJAX 请求上提交。当然,一种解决方案可能是修改您的应用程序并确保每个 AJAX 请求始终如此。否则,这也会丢弃基于请求参数的全局过滤器,因为可能并不总是存在租户 ID。

我认为最好的选择是在包含tenantId的URL中添加一个段。例如,将默认路由替换为以下路由(如果您有许多不同的路由,则需要非常小心以避免路由冲突):

routes.MapRoute(
            name: "Default",
            url: "{tenant}/{controller}/{action}/{id}",
            defaults: new { tenant = "defaultTenant", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

通过这种方式,您可以确保在每个请求中始终提交租户,并且您还可以有 2 个不同的选项卡,其中不同的租户显示每个相应的数据。

关于如何恢复路线段的价值有不同的选择。

绑定将自动填充操作方法上任何名为“租户”的参数的值,或作为操作方法参数的模型类中的任何名为“租户”的参数: public ActionResult Foo(FooModel model, string tenant) { //tenant 和 model.tenant 都将包含 URL 段的值 return View(); }

您还可以编写一个过滤器来访问路由参数的值(RouteData 是作为过滤器方法的参数接收的ActionExecutingContextActionExecutedContext类的属性),并执行一些逻辑。然后过滤器将在您的应用程序或您的基本控制器中设置为全局过滤器:

public class FooFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var tenant = filterContext.RouteData.Values["tenant"]
        //do whatever you need to do before executing the action, based on the tenant 
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var tenant = filterContext.RouteData.Values["tenant"]
        //do whatever you need to do after executing the action, based on the tenant
    }
}

最后一个选项是直接访问基础控制器类上的 RouteData 参数。(因为 RouteData 是基础 MVCController类的属性)

只要您使用 Html 和 Ajax 帮助器生成 URL,URL 的租户段就会保留在您的链接中。但是,如果您有 jquery 代码直接发送带有硬编码 URL 的 AJAX 调用,那么您需要更新该代码以便考虑新的 url 段。

最后,如果tenantId 值不是非常用户友好的值(例如整数),您可以为每个租户设置唯一名称并使用URL 中的名称。然后,您将添加一些逻辑,将其映射到您的应用程序需要的整数值。

于 2013-04-23T20:30:10.500 回答
0

您可以编写自己的过滤器:

如何在 ASP.NET MVC 2 中的每个控制器操作之前执行某些代码? 在任何操作之前执行代码

当然,您的问题没有现成的答案。您需要编写自己的逻辑,如何处理tenantId。例如,在每个操作上,检查它是否不等于当前会话租户 ID 进行重定向。或者把它放在 cookie 中,每次在过滤器中检查 id 是否相等。由你决定。从我的角度来看,cookie 更可取。但它吃交通。

于 2013-04-18T13:37:25.707 回答
-1

您可以在控制器级别应用过滤器并检查正在发送的租户 ID。控制器级别的过滤器不应该比操作过滤器更难。对于我的项目,我需要以类似的方式检查授权,但我覆盖了控制器类,然后从我自己的控制器类继承,因为我有一些非常特殊的需求。

您打算在客户端将租户 ID 存储在哪里?在我看来,您应该使用会话对象来执行此操作。

于 2013-04-16T01:35:53.193 回答