Home » Articles | ASP.NET MVC

ASP.NET MVC Subdomain Routing

14. May 2009 by Juliën Hanssens 10 Comments

One of the propagated “great features” for ASP.NET MVC is the full control you have over the routing and url’s of your webapplication. In order to demonstrate this, let’s walk through a sample that specifically handles subdomain routing.

The Routing Scenario

Actually, the following was a real life scenario: For a client demonstration we wanted to display a simple portal website. We also wanted to show a different subsection of the site that allows the user to test things on a dummy database. And use this View also for a live database connection. In other words, we wanted to distinct the “default", “develop” and “live” site. Without hassling with .config files it was decided that the URL that was entered would be leading.

This is the functional requirement:

Let’s fix this in the following four steps.

Step 1: Custom RouteBase

The first step is to create a standard ASP.NET MVC Web Application. After it’s generated,  we need to add a custom RouteBase. This class will override the default instance and handle every request.

Do this by adding the following class to your Global.asax:

public class SubdomainRoute : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        RouteData returnValue = null;
 
        // Retrieve the url - and split by dots:
        var url = httpContext.Request.Headers["HOST"];
        var index = url.IndexOf(".");
 
        // Determine if a subdomain is provided:
        if (index < 0)
            return returnValue;
 
        // Get the subdomain (as a string):
        string subDomain = url.Substring(0, index);          
 
        // switch through each possible subdomain:
        switch (subDomain.ToLowerInvariant())
        {
            case "develop":
                returnValue = new RouteData(this, new MvcRouteHandler());
                returnValue.Values.Add("controller", "Database");
                returnValue.Values.Add("action", "Index");
                returnValue.Values.Add("liveMode", false); // set parameter to 'false';
                break;
            case "live":
                returnValue = new RouteData(this, new MvcRouteHandler());
                returnValue.Values.Add("controller", "Database");
                returnValue.Values.Add("action", "Index");
                returnValue.Values.Add("liveMode", true); // set parameter to 'true';
                break;
            default: // not a supported domain, return null;
                break;
        }
 
        return returnValue;
    }
 
    /// <summary>
    /// Required override. Just return null ;)
    /// </summary>
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }

This sample is a variation on a StackOverflow post by Jon Cahill. Once registered and enabled (see Step 3 below) it will handle every incoming request and verify the following criteria:

  1. Is a subdomain provided in the url?
  2. If not, return the default (null) RouteBase
  3. If yes, determine – using the case switch, if it is “develop.localhost” or “live.localhost”

One moment: filtering hardcoded domains through string manipulation?! Yeah, this is the consequence. Just swallow it.

Step 2: Create the Controller

Now at this stage we have handled the routing of the url, but it does not do anything special at all. It just doesn’t throw an error when the subdomain is provided. So, let’s point the subdomains – and only the subdomains – to a simple controller called “DatabaseController”. Like such:

public ActionResult Index(bool? liveMode)
{
    if (liveMode != null && liveMode.Value)
        ViewData["connection"] = "Live connection";
    else
        ViewData["connection"] = "Develop connection";
 
    return View();
}

As you can see from this sample it “does something different” based on the nullable parameter liveMode.

Having said that, don’t forget to also add a View() for this Index() action, such as:

<p>Connection type: <strong><% = ViewData[“connection”] %></strong></p>

Here we spit out the results of our work, just to proof it works.

Step 3: Register the Routes

Now that we have the custom RouteBase, Controller and View in place, we can register the specific route in our Global.asax:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    // Register the custom routing class:
    routes.Add(new SubdomainRoute());
 
    // Add the controller:
    routes.MapRoute(
        "Database",
        "{controller}/{action}/{id}",
        new { controller = "Database", action = "Index", liveMode = false }
    );
 
    // ... etc.

Note: don’t forget the parameter “liveMode = false” in this snippet. This is required for the controller. Logically, this is not the only way to go. You can choose to differ to different controllers, or even Action methods. I simply took this path to emphasize the option that parameters still exist – and to minimize the amount of code lines.

That would be about everything to fluff in code. But in order to [F5] this sample on your localhost, it requires just one more minor step: let IIS recognize the subdomain.

Step 4: Subdomains on localhost IIS

On your IIS deployment server you probably have the option to add host headers, but to test locally on your development machine there is a much easier solution. You just edit your hosts file.

Try the following:

  1. Find your hosts file (usually in: %WINDIR%\system32\drivers\etc\)
  2. Edit it with Notepad and add the following lines:

    127.0.0.1     localhost
    127.0.0.1     develop.localhost
    127.0.0.1     live.localhost
  3. Don’t forget when you’re done to delete or comment-out these lines (using # as a prefix)!

You can now easily test the subdomains on your localhost and run the complete sample!

UPDATE: My collegue Maarten Balliauw wrote a more indepth article on this matter.

Resources

kick it on DotNetKicks.com

Comments

Maarten Balliauw
Belgium Maarten Balliauw said:

Problem is this one:
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }

Check your links generated. They will once become like http://live.localhost/Home/Index/?liveMode=false --> ugly!

I'm in discussion with the ASP.NET MVC team on this, but a solution would mean rewriting part of the routing engine. They hate me for this Smile

Juliën Hanssens
Netherlands Juliën Hanssens said:

@MaartenBalliauw
You are right. This is a major drag. However, it's also a major request (++) and I have full confidence that a solution will be presented for this.

trackback
DotNetKicks.com said:

ASP.NET MVC Subdomain Routing

You've been kicked (a good thing) - Trackback from DotNetKicks.com

trackback
DotNetBurner - ASP.net MVC said:

Securancy Blogs | ASP.NET MVC Subdomain Routing

DotNetBurner - burning hot .net content

Craig Stuntz
United States Craig Stuntz said:

Seems to me the workaround is to use route data tokens (similar to how namespaces are presently handled) instead of route values. Of course, that means no parameter binding. But at least it doesn't mung your URIs.

trackback
DotNetShoutout said:

Securancy Blogs | ASP.NET MVC Subdomain Routing

Thank you for submitting this cool story - Trackback from DotNetShoutout

Eric
United States Eric said:

Is a custom RouteBase class really the best place for this?  You don't seem to be doing anything special with routing (the controller and action names are the same).



Orion
Czech Republic Orion said:

I im in almost same problem now. Is there any progress with subdodmain urls? Will it be fixed in mvc 2? Thx

Juliën Hanssens
Netherlands Juliën Hanssens said:

@Orion: Unfortunately, up to this point I am not aware of any fundamental routing breakthroughs with MVC2 on this specific matter.

trackback
Thomas goes .NET said:

ASP.NET MVC - Subdomain auslesen

Für Webapplikationen, die mehrere "Mandanten" verwalten, ist es inzwischen üblich, diese "Instanzen" über die URL durch Subdomains zu unterscheiden, beispielsweise über kunde.applikation.domain. Dieser Post soll kurz zeigen, wie man die Sache mit ASP ...

Comments are closed