Handling sub-domains in ASP.Net MVC
In SaasApp which is framework for mulituser-multitenant SAAS framework for ASP.Net MVC, what I wanted was that when user types the url without a subdomain my app should display the website where user can get details about the product and plans, select a plan and register an account (in this post account refers to say a company that can have multiple users) with selected plan. Once an account is created their users should be able to go to their selected subdomain like myaccount.[SAAS app domain].com, login and access the product. I wanted the subbdomain “myaccount” to be forwarded as a parameter to required actions in the controller to identify the account I am dealing with.
Demo: http://saasapp.yatendra.com
ASP.Net MVC has a sophisticated and flexible routing mechanism. It allows developers to create their own customized routing system by extending RouteBase class and providing your own implementation of GetRouteData(HttpContextBase) function.
Following is what I have used -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SaasApp.Utility;
namespace System.Web.Routing
{
public class DomainRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//get subdomain, the string before first dot in the url
string subdomain = UtilityHelper.GetSubdomain(httpContext.Request.Headers[“HOST”]);
RouteData routeData = new RouteData(this, new MvcRouteHandler());
if (!string.IsNullOrEmpty(subdomain))
{
//url has subdomain
routeData.Values.Add(“account”, subdomain);
string filepath = httpContext.Request.FilePath;
string[] parts = filepath.Split(‘/’);
switch (parts[1].ToLower())
{
case “app”:
routeData.Values.Add(“controller”, “App”);
if (parts.Length > 2)
{
switch (parts[2].ToLower())
{
case “index”:
routeData.Values.Add(“action”, “Index”);
break;
case “about”:
routeData.Values.Add(“action”, “About”);
break;
default:
routeData.Values.Add(“action”, “Index”);
break;
}
}
else
{
routeData.Values.Add(“action”, “Index”);
}
break;
case “account”:
routeData.Values.Add(“controller”, “Account”);
if (parts.Length > 2)
{
switch (parts[2].ToLower())
{
case “login”:
routeData.Values.Add(“action”, “Login”);
break;
case “logout”:
routeData.Values.Add(“action”, “Logout”);
break;
}
}
break;
default:
if (httpContext.Request.IsAuthenticated)
{
httpContext.Response.Redirect(“/App/Index”);
}
httpContext.Response.Redirect(“/Account/Login”);
break;
}
}
else
{
//url does not have subdomain
string filepath = httpContext.Request.FilePath;
string[] parts = filepath.Split(‘/’);
switch (parts[1].ToLower())
{
case “home”:
routeData.Values.Add(“controller”, “Home”);
if (parts.Length > 2)
{
switch (parts[2].ToLower())
{
case “index”:
routeData.Values.Add(“action”, “Index”);
break;
case “plans”:
routeData.Values.Add(“action”, “Plans”);
break;
case “selectplan”:
routeData.Values.Add(“action”, “SelectPlan”);
if (parts.Length > 3)
{
routeData.Values.Add(“planType”, parts[3]);
}
else
{
routeData.Values.Add(“planType”, “1”);
}
break;
case “selectaplan”:
routeData.Values.Add(“action”, “SelectAPlan”);
break;
case “accountcreated”:
routeData.Values.Add(“action”, “AccountCreated”);
if (parts.Length > 3)
{
routeData.Values.Add(“accountName”, parts[3]);
}
else
{
routeData.Values.Add(“accountName”, “”);
}
break;
case “about”:
routeData.Values.Add(“action”, “About”);
break;
default:
routeData.Values.Add(“action”, “Index”);
break;
}
}
else
{
routeData.Values.Add(“action”, “Index”);
}
break;
default:
httpContext.Response.Redirect(“/Home/Index”);
break;
}
}
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
}