Multi-tenant IoC strategy

Feb 28, 2013 at 11:04 PM
Steven,
Excellent work here! Was wondering if you had any recommendations for a multi-tenant strategy that is coordinated by the IoC container. I'm wondering if it could be decorated or maybe intercepted to make sure the command or queries that are being performed are correctly aligned to the client's databases.

The code/features/functionality is identically, just the data is different. They way I'm thinking of determining which tenant it is, is through the sub domain in the MVC application.

So, I was reading this article:
http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Simple-approach-to-multi-tenancy-in-ASPNET-MVC-Part-2.aspx

.. and was curious if it would be overkill and with a ITenantContextFactory could the trick which would get the id of the tenant from the subdomain.

Any thoughts or pointers or any experience with this?

Thanks for any input!
vtali
Coordinator
Mar 1, 2013 at 4:42 AM
Edited Apr 18, 2014 at 7:05 AM
As always there are several path to walk here. From an application's perspective, the easiest thing to do would be to give each tenant its own app domain/site. This way you can you startup switches based on the tenant and have one single DI container running, like this:
if (tenantId == Tenant1) {
    container.RegisterDecorator(typeof(IValidator<>), Tenant1Validator<>);
}
Operations will probably not be happy with this, and another path is to have all tenants in the same app domain and have one container per tenant (but never create one container per request, that will drain performance). I think the approach in the weblog you link at is fine, although the blog post seems to be written before MVC3 was there. For MVC3, it would be easiest to register a special DependencyResolver that allows delegating to the right container instance. Something like this;
public class MultiTenantDependencyResolver : IDependencyResolver
{
    Func<int> tenantIdSelector,;
    IDictionary<int, Container> tenantContainers;

    public MultiTenantDependencyResolver(
        Func<int> tenantIdSelector,
        IDictionary<int, Container> tenantContainers)
    {
        this.tenantIdSelector = tenantIdSelector;
        this.tenantContainers = tenantContainers;
    }

    private Container CurrentContainer 
    { 
        get { return this.tenantContainers[tenantIdSelector()]; }
    }

    private IServiceProvider CurrentServiceProvider
    {
        get { return this.CurrentContainer; }
    }

    public object GetService(Type serviceType)
    {
        return this.CurrentServiceProvider.GetService(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return this.CurrentContainer.GetAllInstances(serviceType);
    }
}
I hope this helps.
Mar 1, 2013 at 11:46 AM
Thank you kindly!
Not only are your words helpful but so is the work you do. SimpleInjector has allowed me to solve many design issues with your instructions in the documentation. Again, can't thank you enough.
Mar 1, 2013 at 1:46 PM
How would this resolve be registered with SimpleInjector:

In Global:

I have DependencyResolver.SetResolver(new MultiTenantResolver( --- interfaces here -- )

any suggestions?

Thanks!
Coordinator
Mar 1, 2013 at 8:08 PM
Edited Mar 4, 2013 at 12:04 PM
I would this registration to look a bit like this:
var tenantContainers = new Dictionary<int, Container>
{
    { Tenants.AbcId, BuildContainer(RegisterForTenantAbc) },
    { Tenants.KlmId, BuildContainer(RegisterForTenantKlm) },
    { Tenants.XyzId, BuildContainer(RegisterForTenantXyz) },
};

var multiTenantResolver = new MultiTenantResolver(
    () => GetTenantIdFromUrl(), tenantContainers);

DependencyResolver.SetResolver(multiTenantResolver);
The BuildContainer and RegisterForTenantAbc methods would look like this:
private static Container BuildContainer(
    Action<Container> tenantSpecificRegistrations)
{
    var container = new Container();

    // TODO: Tenant agnostic registrations. For instance
    container.Register<ITimeProvider, SystemTimeProvider>(
        Lifestyle.Singleton);

    tenantSpecificRegistrations(container);

    container.Verify();

    return container;
}

private static void RegisterForTenantAbc(Container container)
{
    // TODO: regisrations for ABC tenant. For instance
    container.Register<ILogger, AbcTenantLogger>();
}
Does this help?
Marked as answer by dot_NET_Junkie on 11/5/2013 at 7:36 AM
Mar 1, 2013 at 10:15 PM
Steven,
Yes it does! Didn't register that I could pass Func<>. Again, thank you so much!

vtali