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:
- Is a subdomain provided in the url?
- If not, return the default (null) RouteBase
- 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:
- Find your hosts file (usually in: %WINDIR%\system32\drivers\etc\)
- 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
- 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
