MVC3 - 多租户应用程序

发布于 2024-12-28 20:27:39 字数 407 浏览 3 评论 0原文

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

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

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

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

I am building a multi-tenant MVC3 application. What is the best practice for when to establish the tenant context?

I initially considered using dependency injection at application start, but that won't work. At application start I know I can bind the "application" context (or master lookup database), because that only changes by server environment. But the tenant context can change on a request-by-request basis and should be persisted either through an encrypted cookie or the http session, I suppose. I don't think TempData, ViewData, ViewBag will work for me here.

So my question is, on every request I need to validate if the Tenant context exists. If so, grab it from the persistence mechanism. Otherwise establish it. At what point in the MVC pipeline should this be checked?

Should I create a default controller, an action filter that supplies the check/establishment of the tenant, and decorate the controller with the action filter, then have every controller derive from the default controller?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我不是你的备胎 2025-01-04 20:27:39

使用 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;
    }
}

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

这里有一些有用的链接:

通过继承视图类在视图中使用依赖注入:查看链接

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

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

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

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

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

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

With Ninject you can use dependency injection on a request-by-request basis to resolve which tenant the request is for.

The way I did it was to add NinjectMVC3 using Nuget to my project which then adds an App_Start/NinjectMVC3 class. This class contains a RegisterServices(IKernel kernel) routine where you can register your dependencies.

I specified to load my dependencies within a module rather than directly in this routine:

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

The module was then specified as:

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

The base configuration classes can be specified as:

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

And then the actual configuration specified:

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" };
    }
}

The provider can then be written:

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

Then you can inject the configuration into the controllers, views, attributes, etc as required.

Here are some useful links:

To use dependency injection in your views by inheriting from a view class: see link

To see a description and a multi tenancy sample application: see link

There is quite a lot to the example by zowens but implementing multitenancy in asp.net mvc is not a simple task. This example provides some good ideas but I used this as a basis for implementing my own. This example stores each tenant configuration in a seperate c# project and then on start up searches the configurations (you can use reflection for this) to find all tenants to be used. Each tenant configuration project can then store the settings, views, overriden controllers, css, images, javascript specific to that tenant without requiring changes to the main application. This example also uses StructureMap for dependency injection. I opted for Ninject but you could probably use whatever you like as long as it resolves on a request basis.

This example also uses the Spark View engine so that views may easily be stored in other projects. I wanted to stick with the razor view engine but this is a little more tricky as the views have to be precompiled. For this I used David Ebbos razor generator which is an excellent view compiler supplied with precompiled view engine: see link

Note: If you do go down the path of trying to implement your views in a seperate project it can be quite tricky to implement the view engine correctly. I had to implement my own view engine and virtual path factory in order to get it working but it was worth the hassle.

Also, if you implement your resources in a seperate project then the following link may be a useful suggestion on how to retrieve the information from those projects: see link

I hope also this helps with implementing multitenancy in your mvc3 application. Unfortunately I do not have a sample application myself that I can upload somewhere as my implementation is wrapped in with the implementation in the work project.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文