【问题标题】:How to set up a multitenancy application using ASP.NET MVC?如何使用 ASP.NET MVC 设置多租户应用程序?
【发布时间】:2014-08-29 19:02:40
【问题描述】:

multitenancy 应用程序是由多个组织(医疗机构、律师事务所..)共享的应用程序,而每个组织又拥有自己的用户。他们都登录一个集中的环境。

要在应用程序中标识,组织必须在 URL 中表示。有两种主要的 URL 形式。子域和文件夹:

  • [tenancy_name].appname.com/projects/view/123
  • www.appname.com/[tenancy_name]/projects/view/123

起初我尝试了第二个,因为这个解决方案不涉及处理 DNS。但随之而来的问题是:每次开发人员需要表达一个 url(@Html.Action@Url.Action)时,它都必须显式传递 [tenancy_name]。这给开发增加了不必要的开销。一种可能的解决方法是实现这些 HTML 帮助程序的自定义版本,这些帮助程序会自动考虑租户名称。我正在考虑这个选项,但正在寻找更简单的东西。我还意识到 ASP.NET MVC 会自动为传出 URL 传递路由值,但前提是控制器和操作与当前相同。如果路由值总是被传递就好了。

我认为,要实现第一个选项,即子域,我需要一些第三方 DNS 管理器。我听说过 DynDNS 并查看了它,但我认为仅查看他们的站点就不清楚它们是如何工作的。每次创建新租户时,我是否需要触发 Web 服务来告诉他们创建另一个子域?它们是否支持 DNS 中的通配符?它们是否在 Windows Azure 或共享主机上工作?

我在这里寻找方向。我应该走哪条路?

【问题讨论】:

  • 当你说“必须表达”时......我想你的意思是你想那样做,不是吗?因为您可能有一个多租户应用程序,并且不反映在路由或 URL 中。
  • 好吧,软的。我认为它更可取。想象一下这个软件是一个项目经理。如果 URL 中没有表示租户,那么 URL www.appname.com/projects 将根据登录的用户列出不同的项目。如果您有两个帐户,您可能会感到困惑并且很难为 URL 添加书签。事实上,这不是强制性的,而是更可取的。
  • 是的,你提出了一个关于多个帐户书签的好点......在这些情况下它很有用。
  • 您是否已经研究过 MVC 领域?

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


【解决方案1】:

在 codeplex 上查看这个项目,“baseRoute”也许可以帮助你。

http://mvccoderouting.codeplex.com/

问候。

【讨论】:

    【解决方案2】:

    以下使我们的应用中的视图分辨率变得微不足道:

    使用方法: 对于您需要为特定租户重载的视图 - 以与自定义显示模式相同的方式对待它们: 以下将起作用:

    Index.cshtml
    Index.cust2.mobile.cshtml
    

    Partials/CustomerAgreement.cust1.cshtml
    Partials/CustomerAgreement.cust2.cshtml
    

    据我所知,显示/编辑器模板也以同样的方式工作

    已知问题: 1.您必须为主要+次要的所有组合创建布局(无论出于何种MVC原因) 2. 不管 resharper 对显示模式的支持怎么说——它不支持“.”。作为显示模式名称的一部分(这是一个跟踪进度的问题http://youtrack.jetbrains.com/issue/RSRP-422413

    //put in application start --------
    
    DisplayModeProvider.Instance.Modes.Clear();
    foreach (var displayMode in GetDisplayModes())
    {
        DisplayModeProvider.Instance.Modes.Add(displayMode);
    }
    
    private IEnumerable<IDisplayMode> GetDisplayModes()
    {
        return new CompoundDisplayModeBuilder()
            .AddPrimaryFilter(_ => dependencyResolver.GetService(typeof(IResolveCustomerFromUrl)).GetName(),
                "cust1",
                "cust2")
            .AddSecondaryFilter(ctx => ctx.Request.Browser.IsMobileDevice, "mobile")
            .BuildDisplayModes();
    }
    
    //end of application start part
    
    
    //and the mode builder implementation:
    public class CompoundDisplayModeBuilder
    {
        private readonly IList<DefaultDisplayMode> _primaryDisplayModes = new List<DefaultDisplayMode>();
        private readonly IList<DefaultDisplayMode> _secondaryDisplayModes = new List<DefaultDisplayMode>();
    
        //NOTE: this is just a helper method to make it easier to specify multiple tenants in 1 line in global asax
        //You can as well remove it and add all tenants one by one, especially if resolution delegates are different
        public CompoundDisplayModeBuilder AddPrimaryFilter(Func<HttpContextBase, string> contextEval, params string[] valuesAsSuffixes)
        {
            foreach (var suffix in valuesAsSuffixes)
            {
                var val = suffix;
                AddPrimaryFilter(ctx => string.Equals(contextEval(ctx), val, StringComparison.InvariantCultureIgnoreCase), val);
            }
    
            return this;
        }
    
        public CompoundDisplayModeBuilder AddPrimaryFilter(Func<HttpContextBase, bool> contextCondition, string suffix)
        {
            _primaryDisplayModes.Add(new DefaultDisplayMode(suffix) { ContextCondition = contextCondition });
            return this;
        }
    
        public CompoundDisplayModeBuilder AddSecondaryFilter(Func<HttpContextBase, bool> contextCondition, string suffix)
        {
            _secondaryDisplayModes.Add(new DefaultDisplayMode(suffix) { ContextCondition = contextCondition });
            return this;
        }
    
        public IEnumerable<IDisplayMode> BuildDisplayModes()
        {
            foreach (var primaryMode in _primaryDisplayModes)
            {
                var primaryCondition = primaryMode.ContextCondition;
                foreach (var secondaryMode in _secondaryDisplayModes)
                {
                    var secondaryCondition = secondaryMode.ContextCondition;
                    yield return new DefaultDisplayMode(primaryMode.DisplayModeId + "." + secondaryMode.DisplayModeId){
                        ContextCondition = ctx => primaryCondition(ctx) && secondaryCondition(ctx)
                    };
                }
            }
    
            foreach (var primaryFilter in _primaryDisplayModes)
            {
                yield return primaryFilter;
            }
    
            foreach (var secondaryFilter in _secondaryDisplayModes)
            {
                yield return secondaryFilter;
            }
    
            yield return new DefaultDisplayMode();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-06
      • 1970-01-01
      • 2020-06-18
      • 2011-05-05
      • 1970-01-01
      相关资源
      最近更新 更多