如何使用ASP.NET MVC设置多租户应用程序?

3
一个多租户应用程序是一款由多个组织(医疗实践、法律办公室等)共享的应用程序,每个组织都有自己的用户。他们都登录到一个集中的环境中。
为了在应用程序中被识别,组织必须在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或共享托管吗?

我在这里寻找方向。我应该怎么走?


1
当你说“必须表达”时...我想你的意思是你想以那种方式做,对吗?因为你可以有一个多租户应用程序,但不在路由或URL中反映它。 - Romias
2
嗯,有点吧。我觉得这样更好。想象一下,软件就像一个项目经理。如果租赁在URL中没有表达出来,那么URL www.appname.com/projects会根据登录的用户列出不同的项目。如果你有两个账户,可能会感到困惑,并且难以为URL添加书签。事实上,这并不是强制性的,但是推荐使用。 - Andre Pena
是的,你提到了多个账户和书签这一点很有道理...在这些情况下它非常有用。 - Romias
你已经研究过MVC区域了吗? - BigMike
2个回答

1

0

在我们的应用程序中,以下内容使视图分辨率变得微不足道:

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

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();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接