Reputation: 61
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?
Upvotes: 3
Views: 1605
Reputation: 4909
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.
Upvotes: 4