【问题标题】:Tenant-specific routes for dynamically loaded modules动态加载模块的租户特定路由
【发布时间】:2024-01-17 05:02:01
【问题描述】:

我正在使用 ASP.NET MVC 开发应用程序框架。本质上,最终目标是能够登录到管理界面,使用自定义设置创建新租户,启用他们想要的模块(博客、购物篮等)......完成工作 - 新网站让客户满意。我没有使用单独的应用程序,因为会有很多共享代码,这样维护起来会更容易,还因为在高峰时段上线一个新的、相同的节点非常容易。

根据为租户加载的模块,不同的路由适用于每个租户。在我看来,有三种选择:

  • 让所有租户共享同一个路由集合 - 但是,如果有很多模块,它会搜索很多不需要的路由,并且某些模块很可能有冲突的路由。

  • 将每个租户的必要路由添加到全局路由集合中,并扩展路由类以查看域 - 但随着更多租户的添加,这可能很快会产生数百条路由。

  • 首先确定正在访问的租户,然后只搜索他们自己的私有路由集合 - 这将是理想的,但我已经搜索了几个小时,完全不知道该怎么做!

那么任何人都可以为我指出第三个选项的正确方向或解释为什么前两个选项中的任何一个都没有那么糟糕?

【问题讨论】:

    标签: asp.net asp.net-mvc asp.net-mvc-routing multi-tenant


    【解决方案1】:

    如何在您的应用中区分每个网站?如果我们假设每个租户将由一个唯一的域名或子域名标识,那么您可以使用一个路由和一些RouteConstraints 来完成您的路由。创建两个约束,一个用于控制器,另一个用于操作。假设您的数据库中有列出特定租户可用控制器/操作的表,您的约束如下:

    using System; 
    using System.Web; 
    using System.Web.Routing;  
    
    namespace ExampleApp.Extensions 
    { 
        public class IsControllerValidForTenant : IRouteConstraint
        {
            public IsControllerValidForTenant() { }
    
            private DbEntities _db = new DbEntities();
    
            public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
            {
                // determine domain
                var domainName = httpContext.Request.Url.DnsSafeHost;
                var siteId = _db.Sites.FirstorDefault(s => s.DomainName == domainName).SiteId;
                // passed constraint if this controller is valid for this tenant
                return (_db.SiteControllers.Where(sc => sc.Controller == values[parameterName].ToString() && sc.SiteId == siteId).Count() > 0);
            }
        }
    
        public class IsActionValidForTenant : IRouteConstraint
        {
            public IsActionValidForTenant() { }
    
            private DbEntities _db = new DbEntities();
    
            public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
            {
                // determine domain
                var domainName = httpContext.Request.Url.DnsSafeHost;
                var siteId = _db.Sites.FirstorDefault(s => s.DomainName == domainName).SiteId;
                // passed constraint if this action is valid for this tenant
                return (_db.SiteActions.Where(sa => sa.Action == values[parameterName].ToString() && sa.SiteId == siteId).Count() > 0);
            }
        }
    }
    

    然后,在 Global.asax.cs 中,按如下方式定义您的路线:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
            new { controller = new IsControllerValidForTenant(), action = new IsActionValidForTenant(),}
        );
    }
    

    当请求进来时,约束将检查控制器和操作是否对域有效,因此只有该租户的有效控制器和操作才会通过 RouteConstraints。

    【讨论】:

    • 感谢您的详细回复。这在我的问题中属于选项 1,我曾想过实施自己的路线约束。这仍然意味着,如果我有很多模块,就会有很多不必要的搜索路径 - 这很糟糕吗?
    • 穿越路线的成本是最低的。但是,鉴于您提供的信息,您为什么认为会有这么多的路线?显然您需要很多路由,特别是如果您使用 RouteConstraints 或使用自定义 RouteHandler(正如我在 *.com/questions/7222533/polymorphic-model-binding/… 中讨论的那样)。
    • 我想我在想,如果有很多模块,可能会有大量路由,这些模块至少有一条来自其区域注册的路由。我想,虽然在更大的计划中,从长远来看,我不会有 那么多 模块,如果它像你说的那样快,我也可以按照你的方式去做。我可能会使用动作过滤器来打开或关闭控制器/动作,但我非常喜欢你的方式。感谢您的帮助:)