0

I am using ASP.NET MVC to develop an application framework. Essentially, the end goal is to be able to log into an admin interface, create a new tenant with custom settings, enable the modules they want (blog, shopping basket, etc)... job done - satisfied customer with new website. I'm not using separate applications because there will be a lot of shared code and it would be easier to maintain this way, and also because it would be pretty easy to bring a new, identical node online at peak times.

Depending on what modules are loaded for the tenant, different routes are applicable for each tenant. As I see it, there are three options:

  • Have all tenants share the same route collection - however if there are a lot of modules it'll be searching through a lot of routes it doesn't need to, and some modules may well have conflicting routes.

  • Add the necessary routes for each tenant to the global route collection and extend the route class to look at the domain as well - but this could quickly end up with hundreds of routes as more tenants are added.

  • Work out what tenant is being accessed first and then only search their own private route collection - this would be ideal, but I've searched for hours and have absolutely no idea how to do it!

So can anyone point me in the correct direction for the third option or explain why either of the first two aren't really that bad?

sjmeverett
  • 1,196
  • 10
  • 21

1 Answers1

0

How will each website be distinguished in your app? If we assume each tenant will be identified by a unique domain name or subdomain name, then you can accomplish your routing with one route and some RouteConstraints. Create two constraints, one for controllers, the other for actions. Assuming that you will have tables in your database which list the available controllers/actions for a specific tenant, your constraints would be as follows:

using System; 
using System.Web; 
using System.Web.Routing;  

namespace ExampleApp.Extensions 
{ 
    public class IsControllerValidForTenant : IRouteConstraint
    {
        public IsControllerValidForTenant() { }

        private DbEntities _db = new DbEntities();

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            // determine domain
            var domainName = httpContext.Request.Url.DnsSafeHost;
            var siteId = _db.Sites.FirstorDefault(s => s.DomainName == domainName).SiteId;
            // passed constraint if this controller is valid for this tenant
            return (_db.SiteControllers.Where(sc => sc.Controller == values[parameterName].ToString() && sc.SiteId == siteId).Count() > 0);
        }
    }

    public class IsActionValidForTenant : IRouteConstraint
    {
        public IsActionValidForTenant() { }

        private DbEntities _db = new DbEntities();

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            // determine domain
            var domainName = httpContext.Request.Url.DnsSafeHost;
            var siteId = _db.Sites.FirstorDefault(s => s.DomainName == domainName).SiteId;
            // passed constraint if this action is valid for this tenant
            return (_db.SiteActions.Where(sa => sa.Action == values[parameterName].ToString() && sa.SiteId == siteId).Count() > 0);
        }
    }
}

Then, in Global.asax.cs, define your route as follows:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
        new { controller = new IsControllerValidForTenant(), action = new IsActionValidForTenant(),}
    );
}

When a request comes in, the constraints will examine whether the controller and action are valid for the domain, so that only valid controllers and actions for that tenant will pass the RouteConstraints.

counsellorben
  • 10,704
  • 3
  • 37
  • 38
  • Thanks for your detailed reply. This comes under option 1 in my question, and I had thought of implementing my own route constraint. It still means though that if I have a lot of modules there are a lot of routes to unnecessarily search through - is this bad? – sjmeverett Aug 30 '11 at 20:29
  • The cost of running through routes is minimal. However, given the information you provided, why do you believe that there will be such a large number of routes? It is not evident that you will need many routes, particularly if you use RouteConstraints or if you use a custom `RouteHandler` (as I discuss in http://stackoverflow.com/questions/7222533/polymorphic-model-binding/7222990#7222990). – counsellorben Aug 30 '11 at 20:35
  • I guess I was thinking there could be a large number of routes if there was a lot of modules, which would have at the very least a route from their area registration. I suppose though in the greater scheme of things, I'm not going to have *that* many modules in the long run, and if it's quick enough as you say I may as well just do it your way. I was probably going to use an action filter to switch controllers/actions on or off, but I quite like your way. Thanks for your help :) – sjmeverett Aug 30 '11 at 21:20