1

I'm using asp.net mvc 4 to develop a multi-tenant mvc application.

I am using Autofac for the IOC container and have configured controllers to be registered for each client in different assemblies.

Autofac will switch out which controller it returns when asked to resolve depending on the current client context, which is determineid by looking at the route data.

I am getting an exception

Multiple types were found that match the controller named 'Home'.

Which would seem to indicate that Autofac is returning more than one match, however on closer inspection it seems that MVC is not even calling Autofac for controller resolution.

I can see a call that asks for an IControllerFactory from the DependencyResolver but never for the controller itself.

Do I need to implement my own controller factory ontop of using the dependency resolver?

I have had a look through the source code of mvc and from what I can tell the DefaultControllerFactory uses the dependencyresolver and should resolve the controllers but I'm having trouble debugging into it to see exactly whats happenning.

The tenant dlls are not referenced by the main website but copied in after build.

My global.asax looks as follows: // First, create your application-level defaults using a standard // ContainerBuilder, just as you are used to. var builder = new ContainerBuilder(); var appContainer = builder.Build();

// Once you've built the application-level default container, you
// need to create a tenant identification strategy. The details of this
// are discussed later on.
var tenantIdentifier = new RouteDataTenantIdentificationStrategy();

// Now create the multitenant container using the application
// container and the tenant identification strategy.
var mtc = new MultitenantContainer(tenantIdentifier, appContainer);

// Configure the overrides for each tenant by passing in the tenant ID
// and a lambda that takes a ContainerBuilder.

var assemblies = AppDomain.CurrentDomain.GetAssemblies()
    .AsQueryable()
    .Where(a => a.IsDefined(typeof (PluginAssemblyAttribute), false));


foreach (var assembly in assemblies)
{
    var pluginSpecification =
        assembly.GetCustomAttributes(typeof(PluginAssemblyAttribute), false)
            .OfType<PluginAssemblyAttribute>()
            .Single();
    var assembly1 = assembly;
    mtc.ConfigureTenant(pluginSpecification.Tenant, 
        b => b.RegisterControllers(assembly1));
}

DependencyResolver.SetResolver(new AutofacDependencyResolver(mtc));

The tenant identification strategy looks as follows:

public class RouteDataTenantIdentificationStrategy 
    : ITenantIdentificationStrategy
{
    public bool TryIdentifyTenant(out object tenantId)
    {
        tenantId = null;
        var context = HttpContext.Current;
        try
        {
            if (context == null || context.Request == null)
            {
                return false;
            }
        }
        catch (HttpException)
        {
            // This will happen at application startup in MVC3
            // integration since the ILifetimeScopeProvider tries
            // to be resolved from the container at the point where
            // a new AutofacDependencyResolver is created.
            tenantId = null;
            return false;
        }

        var routeData = RouteTable.Routes.GetRouteData(
            new HttpContextWrapper(context));

        if (routeData != null)
            tenantId = routeData.Values.ContainsKey("tenant") ? 
                routeData.Values["tenant"] : null;

        return tenantId != null;
    }
}

EDIT: The full exception is

The request for 'Home' has found the following matching controllers:
MultiTenantViewEngine.Web.Controllers.HomeController
MultiTenantViewEngine.Web.Tenant1.Controllers.HomeController]
   System.Web.Mvc.DefaultControllerFactory.GetControllerTypeWithinNamespaces(RouteBase route, String controllerName, HashSet`1 namespaces) +230
   System.Web.Mvc.DefaultControllerFactory.GetControllerType(RequestContext requestContext, String controllerName) +833
   System.Web.Mvc.DefaultControllerFactory.System.Web.Mvc.IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, String controllerName) +196
   System.Web.Mvc.MvcRouteHandler.GetSessionStateBehavior(RequestContext requestContext) +267
   System.Web.Mvc.MvcRouteHandler.GetHttpHandler(RequestContext requestContext) +61
   System.Web.Mvc.MvcRouteHandler.System.Web.Routing.IRouteHandler.GetHttpHandler(RequestContext requestContext) +44
   System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +352
   System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e) +144
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +239
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +114

Looking at this in more depth it would seem to indicate that this error happens because GetControllerTypeWithinNamespaces() is returning more than one namespace.

Is there a way to overcome this?

5
  • What does your RouteDataTenantIdentificationStrategy look like, and have you put a breakpoint in it to see if it is being called when the controller is resolved? Commented Feb 18, 2013 at 4:27
  • just added it, will double check to see if its being called Commented Feb 18, 2013 at 4:55
  • it appears it is being called and returning the correct tenantid Commented Feb 18, 2013 at 5:03
  • If you haven't yet, you should check out Zack Owens' piece on this. weblogs.asp.net/zowens/archive/2010/05/26/… Commented Mar 14, 2013 at 17:41
  • Yeah I saw that probably is the route I will end up going down Commented Mar 14, 2013 at 22:13

1 Answer 1

0

You have to specify namespace in all of your routes:

routes.MapRoute(
"Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" },  // Parameter defaults
    "Some.NameSpace.To.Controllers" // required
);

If you would like to use the home controller in the tenant DLL: either redirect to it from the main site HomeController or create your custom route (inherit from Route) and do the namespace selection in it.

Sign up to request clarification or add additional context in comments.

3 Comments

Well depending on the context of the application I either want to use the controller in the main site dll, or in tenant1 dll or another tenant dll, juding by this it might be best to write my own controller factory that just asks the dependency resolver to give me the types?
I would rather use a custom route since it's the routes that decides which controller to load. The ControllerFactory is just that: A factory constructing the controller which was ordered by it.
and I can dynamically override the namespace to search in for the custom route?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.