Mulit-tenant ASP.NET MVC – Controllers

Posted by zowens on ASP.net Weblogs See other posts from ASP.net Weblogs or by zowens
Published on Mon, 07 Jun 2010 16:40:00 GMT Indexed on 2010/06/07 16:42 UTC
Read the original article Hit count: 1210

Filed under:
|
|
|
|

Part I – Introduction

Part II – Foundation

 

The time has come to talk about controllers in a multi-tenant ASP.NET MVC architecture. This is actually the most critical design decision you will make when dealing with multi-tenancy with MVC. In my design, I took into account the design goals I mentioned in the introduction about inversion of control and what a tenant is to my design. Be aware that this is only one way to achieve multi-tenant controllers.

 

The Premise

MvcEx (which is a sample written by Rob Ashton) utilizes dynamic controllers. Essentially a controller is “dynamic” in that multiple action results can be placed in different “controllers” with the same name. This approach is a bit too complicated for my design. I wanted to stick with plain old inheritance when dealing with controllers. The basic premise of my controller design is that my main host defines a set of universal controllers. It is the responsibility of the tenant to decide if the tenant would like to utilize these core controllers. This can be done either by straight usage of the controller or inheritance for extension of the functionality defined by the controller. The controller is resolved by a StructureMap container that is attached to the tenant, as discussed in Part II.

 

Controller Resolution

I have been thinking about two different ways to resolve controllers with StructureMap. One way is to use named instances. This is a really easy way to simply pull the controller right out of the container without a lot of fuss. I ultimately chose not to use this approach. The reason for this decision is to ensure that the controllers are named properly. If a controller has a different named instance that the controller type, then the resolution has a significant disconnect and there are no guarantees. The final approach, the one utilized by the sample, is to simply pull all controller types and correlate the type with a controller name. This has a bit of a application start performance disadvantage, but is significantly more approachable for maintainability. For example, if I wanted to go back and add a “ControllerName” attribute, I would just have to change the ControllerFactory to suit my needs.

 

The Code

The container factory that I have built is actually pretty simple. That’s really all we need. The most significant method is the GetControllersFor method. This method makes the model from the Container and determines all the concrete types for IController. 

The thing you might notice is that this doesn’t depend on tenants, but rather containers. You could easily use this controller factory for an application that doesn’t utilize multi-tenancy.

public class ContainerControllerFactory : IControllerFactory
{
    private readonly ThreadSafeDictionary<IContainer, IDictionary<string, Type>> typeCache;

    public ContainerControllerFactory(IContainerResolver resolver)
    {
        Ensure.Argument.NotNull(resolver, "resolver");
        this.ContainerResolver = resolver;
        this.typeCache = new ThreadSafeDictionary<IContainer, IDictionary<string, Type>>();
    }

    public IContainerResolver ContainerResolver { get; private set; }

    public virtual IController CreateController(RequestContext requestContext, string controllerName)
    {
        var controllerType = this.GetControllerType(requestContext, controllerName);

        if (controllerType == null)
            return null;

        var controller = this.ContainerResolver.Resolve(requestContext).GetInstance(controllerType) as IController;

        // ensure the action invoker is a ContainerControllerActionInvoker
        if (controller != null && controller is Controller && !((controller as Controller).ActionInvoker is ContainerControllerActionInvoker))
            (controller as Controller).ActionInvoker = new ContainerControllerActionInvoker(this.ContainerResolver);

        return controller;
    }

    public void ReleaseController(IController controller)
    {
        if (controller != null && controller is IDisposable)
            ((IDisposable)controller).Dispose();
    }

    internal static IEnumerable<Type> GetControllersFor(IContainer container)
    {
        Ensure.Argument.NotNull(container);
        return container.Model.InstancesOf<IController>().Select(x => x.ConcreteType).Distinct();
    }

    protected virtual Type GetControllerType(RequestContext requestContext, string controllerName)
    {
        Ensure.Argument.NotNull(requestContext, "requestContext");
        Ensure.Argument.NotNullOrEmpty(controllerName, "controllerName");

        var container = this.ContainerResolver.Resolve(requestContext);

        var typeDictionary = this.typeCache.GetOrAdd(container, () => GetControllersFor(container).ToDictionary(x => ControllerFriendlyName(x.Name)));

        Type found = null;
        if (typeDictionary.TryGetValue(ControllerFriendlyName(controllerName), out found))
            return found;
        return null;
    }

    private static string ControllerFriendlyName(string value)
    {
        return (value ?? string.Empty).ToLowerInvariant().Without("controller");
    }
}

One thing to note about my implementation is that we do not use namespaces that can be utilized in the default ASP.NET MVC controller factory. This is something that I don’t use and have no desire to implement and test. The reason I am not using namespaces in this situation is because each tenant has its own namespaces and the routing would not make sense in this case.

 

Because we are using IoC, dependencies are automatically injected into the constructor. For example, a tenant container could implement it’s own IRepository and a controller could be defined in the “main” project. The IRepository from the tenant would be injected into the main project’s controller. This is quite a useful feature.

 

Again, the source code is on GitHub here.

 

Up Next

Up next is the view resolution. This is a complicated issue, so be prepared. I hope that you have found this series useful. If you have any questions about my implementation so far, send me an email or DM me on Twitter. I have had a lot of great conversations about multi-tenancy so far and I greatly appreciate the feedback!


kick it on DotNetKicks.com

© ASP.net Weblogs or respective owner

Related posts about .NET

Related posts about c#