14
\$\begingroup\$

How do I handle multi-tenancy with routes in ASP.NET MVC?

The application is a multitenant ASP.NET MVC 3 application. Tenants are identified based on hostname so this does not interfere with routing.

Other important information:

  • The application has a number of features (or plugins). These are essentially other web projects with their own controllers, views and routes.

  • ASP.NET MVC routing serves as our entry point into these features.

  • One key point is that all the features function the SAME way for all tenants. The variable is whether the tenant is actually subscribed to the feature.

So imagine we have a "blog" feature with a route like so:

/blog/{instance}/posts

When our application starts we register the routes of all "routable" features. However, we need to ensure that this route is only matched if the tenant is subscribed to that feature.

Currently I'm doing this with a custom route:

private readonly IFeatureMetadata feature;

public FeatureRoute(IFeatureMetadata feature, string url, RouteValueDictionary defaults, IRouteHandler handler) :
    base(url, defaults, handler) 
{
    if (feature == null)
        throw new ArgumentNullException("feature");
    this.feature = feature;
}

public override RouteData GetRouteData(System.Web.HttpContextBase httpContext) 
{
    var routeData = base.GetRouteData(httpContext);

    if (routeData == null)
        return null;

    var context = DependencyResolver.Current.GetService<ISiteContext>();
    var site = context.GetSite();
    var matchedInstances = site.FeatureInstances.Where(f => f.Feature.FeatureType == feature.FeatureType);

    if (!matchedInstances.Any())
        return null;

    if (routeData.Values["instance"] == null) 
    {
        routeData.Values["instance"] = matchedInstances.First().Id;
    }

    return routeData;
}

One thing that I need to cater for is routes that do not specify an instance.

For example, a tenant may have one or more blogs. If he only has one then outgoing routes should just generate:

http://[domain]/blog/posts

If he has more than one then we should generate:

http://[domain]/blog/{instance}/posts

Currently I'm handling this by registering 2 routes (one with the {instance} parameter and one without). In the code above you can see that if the instance parameter doesn't exist in RouteData we just add the id of the first matched feature instance.

This is then sufficient for me to make a call to my blog service:

GetPosts(int featureInstanceId);

While everything "works", I'm looking for feedback on my implementation. As a freelance developer this seems to be the only way I can get my code reviewed.

\$\endgroup\$

migrated from stackoverflow.com May 12 '11 at 21:01

This question came from our site for professional and enthusiast programmers.

2
\$\begingroup\$

You could also create a route constraint that redirects or changes the routedata to "buy module".

http://msdn.microsoft.com/en-us/library/cc668201.aspx#adding_constraints_to_routes

http://www.jfarrell.net/2008/11/asp-net-mvc-authentication-strategies.html

\$\endgroup\$
0
\$\begingroup\$

One way to handle it is to write an ActionFilter that does checking against the list of features the customer owns, and if they don't own that feature, it redirects them to buy it.

\$\endgroup\$
  • 2
    \$\begingroup\$ I prefer to handle this at the routing level as it keeps such logic out of the controllers. We have an account management application for upgrades so I'm too concerned with redirecting if they do not have the feature. \$\endgroup\$ – Ben May 12 '11 at 21:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.