MVC3 - 多租户应用程序

时间:2012-01-25 19:31:20

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

我正在构建一个多租户MVC3应用程序。 建立租户上下文时的最佳做法是什么?

我最初考虑在应用程序启动时使用依赖注入,但这不起作用。在应用程序启动时,我知道我可以绑定“应用程序”上下文(或主查找数据库),因为它只会根据服务器环境进行更改。但是租户上下文可以按请求进行更改,并且应该通过加密cookie或http会话来保留,我想。我不认为TempData,ViewData,ViewBag会在这里为我工作。

所以我的问题是,如果租户上下文存在,我需要验证每个请求。如果是这样,请从持久性机制中获取它。否则建立它。在MVC管道中的哪一点应该检查?

我应该创建一个默认控制器,一个提供租户检查/建立的动作过滤器,并使用动作过滤器装饰控制器,然后让每个控制器都来自默认控制器吗?

1 个答案:

答案 0 :(得分:4)

使用Ninject,您可以逐个请求地使用依赖注入来解析请求所针对的租户。

我这样做的方法是使用Nuget将NinjectMVC3添加到我的项目中,然后添加App_Start/NinjectMVC3类。该类包含一个RegisterServices(IKernel kernel)例程,您可以在其中注册依赖项。

我指定在模块中加载我的依赖项,而不是直接在此例程中加载:

private static void RegisterServices(IKernel kernel)
{
    kernel.Load(new TenantConfigurationModule());
}

然后将模块指定为:

public class TenantConfigurationModule : NinjectModule
{
    public override void Load()
    {
        IEnumerable<ITenantConfiguration> configuration = //Instantiate a list of configuration classes from where they are stored.

        //Initialise a ninject provider to determine what configuration object to bind to on each request.
        TenantConfigurationProvider provider = new TenantConfigurationProvider(configuration);

        //And then bind to the provider specifying that it is on a per request basis.
        Bind<ITenantConfiguration>().ToProvider(provider).InRequestScope();
    }
}

基本配置类可以指定为:

public interface ITenantConfiguration
{
    string TenantName { get; }

    IEnumerable<string> UrlPaths { get; }

    //whatever else you need for the tenant configuration
}


public abstract class TenantConfiguration : ITenantConfiguration
{
    public string TenantName { get; protected set; }

    public IEnumerable<string> UrlPaths { get; protected set; }
}

然后指定实际配置:

public class TenantOneConfiguration : TenantConfiguration
{
    public MVTTenantConfiguration()
    {
        TenantName = "MVT";
        UrlPaths = new string[] { "http://localhost:50094" }; //or whatever the url may be
    }
}

public class TenantOneConfiguration : TenantConfiguration
{
    public MVTTenantConfiguration()
    {
        TenantName = "MVT";
        UrlPaths = new string[] { "http://localhost:50095" };
    }
}

然后可以写提供者:

public class TenantConfigurationProvider : Provider<ITenantConfiguration>
{
    private IEnumerable<ITenantConfiguration> configuration;

    public TenantConfigurationProvider(IEnumerable<ITenantConfiguration> configuration)
    {
        if (configuration == null || configuration.Count() == 0)
        {
            throw new ArgumentNullException("configuration");
        }

        this.configuration = configuration;
    }

    protected override ITenantConfiguration CreateInstance(IContext context)
    {
        //Determine the request base url.
        string baseUrl = string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority);

        //Find the tenant configuration for the given request url.
        ITenantConfiguration tenantConfiguration = configuration.Single(c => c.UrlPaths.Any(p => p.Trim().TrimEnd('/').Equals(baseUrl, StringComparison.OrdinalIgnoreCase)));

        if (tenantConfiguration == null)
        {
            throw new TenantNotFoundException(string.Format("A tenant was not found for baseUrl {0}", baseUrl));
        }

        return tenantConfiguration;
    }
}

然后,您可以根据需要将配置注入控制器,视图,属性等。

以下是一些有用的链接:

通过从视图类继承来在视图中使用依赖项注入:see link

要查看说明和多租户示例应用程序:see link

zowens的例子有很多,但在asp.net mvc中实现多租户并不是一项简单的任务。这个例子提供了一些好的想法,但我用它作为实现我自己的基础。此示例将每个租户配置存储在单独的c#项目中,然后在启动时搜索配置(您可以使用反射)来查找要使用的所有租户。然后,每个租户配置项目可以存储特定于该租户的设置,视图,覆盖控制器,css,图像,javascript,而无需更改主应用程序。此示例还使用StructureMap进行依赖项注入。我选择了Ninject但你可以使用你喜欢的任何东西,只要它能够根据请求做出决定。

此示例还使用Spark View引擎,以便可以轻松地将视图存储在其他项目中。我想坚持使用剃刀视图引擎,但由于必须预先编译视图,因此这有点棘手。为此,我使用了David Ebbos razor生成器,它是一个提供预编译视图引擎的出色视图编译器:see link

注意:如果您确实在单独的项目中尝试实现视图,那么正确实现视图引擎可能会非常棘手。我不得不实现自己的视图引擎和虚拟路径工厂,以使其工作,但值得麻烦。

此外,如果您在单独的项目中实施资源,那么以下链接可能是如何从这些项目中检索信息的有用建议:see link

我希望这也有助于在mvc3应用程序中实现多租户。不幸的是,我自己没有自己的示例应用程序,因为我的实现包含在工作项目中的实现中。