This can be achieved by creating a custom domain route:
public class DomainRoute : Route { private Regex domainRegex; private Regex pathRegex; public string Domain { get; set; } public DomainRoute(string domain, string url, RouteValueDictionary defaults) : base(url, defaults, new MvcRouteHandler()) { Domain = domain; } public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler) { Domain = domain; } public DomainRoute(string domain, string url, object defaults) : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) { Domain = domain; } public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler) : base(url, new RouteValueDictionary(defaults), routeHandler) { Domain = domain; } public override RouteData GetRouteData(HttpContextBase httpContext) { // Build regex domainRegex = CreateRegex(Domain); pathRegex = CreateRegex(Url); // Request information string requestDomain = httpContext.Request.Headers["host"]; if (!string.IsNullOrEmpty(requestDomain)) { if (System.Diagnostics.Debugger.IsAttached == false) { if (requestDomain.IndexOf(":") > 0) { requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":")); } } // Strip Multiple Subdomains if (requestDomain.Split('.').Length > 3) { string[] split = requestDomain.Split('.'); requestDomain = String.Join(".", split, split.Length - 3, 3); string url = String.Format("{0}://{1}/", httpContext.Request.Url.Scheme, requestDomain); if (System.Diagnostics.Debugger.IsAttached == true) { httpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); httpContext.Response.CacheControl = "no-cache"; } httpContext.Response.RedirectPermanent(url, true); } } else { requestDomain = httpContext.Request.Url.Host; } string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; // Match domain and route Match domainMatch = domainRegex.Match(requestDomain); Match pathMatch = pathRegex.Match(requestPath); // Route data RouteData data = null; if (domainMatch.Success && pathMatch.Success) { data = new RouteData(this, RouteHandler); // Add defaults first if (Defaults != null) { foreach (KeyValuePair<string, object> item in Defaults) { data.Values[item.Key] = item.Value; } } // Iterate matching domain groups for (int i = 1; i < domainMatch.Groups.Count; i++) { Group group = domainMatch.Groups[i]; if (group.Success) { string key = domainRegex.GroupNameFromNumber(i); if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) { if (!string.IsNullOrEmpty(group.Value)) { data.Values[key] = group.Value; } } } } // Iterate matching path groups for (int i = 1; i < pathMatch.Groups.Count; i++) { Group group = pathMatch.Groups[i]; if (group.Success) { string key = pathRegex.GroupNameFromNumber(i); if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) { if (!string.IsNullOrEmpty(group.Value)) { data.Values[key] = group.Value; } } } } } return data; } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { return base.GetVirtualPath(requestContext, RemoveDomainTokens(values)); } public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values) { // Build hostname string hostname = values.Aggregate(Domain, (current, pair) => current.Replace("{" + pair.Key + "}", pair.Value.ToString())); // Return domain data return new DomainData { Protocol = "http", HostName = hostname, Fragment = "" }; } private Regex CreateRegex(string source) { // Perform replacements source = source.Replace("/", @"\/?"); source = source.Replace(".", @"\.?"); source = source.Replace("-", @"\-?"); source = source.Replace("{", @"(?<"); source = source.Replace("}", @">([a-zA-Z0-9_]*))"); return new Regex("^" + source + "$"); } private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values) { Regex tokenRegex = new Regex(@"({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?"); Match tokenMatch = tokenRegex.Match(Domain); for (int i = 0; i < tokenMatch.Groups.Count; i++) { Group group = tokenMatch.Groups[i]; if (group.Success) { string key = group.Value.Replace("{", "").Replace("}", ""); if (values.ContainsKey(key)) values.Remove(key); } } return values; } } public class DomainData { public string Protocol { get; set; } public string HostName { get; set; } public string Fragment { get; set; } }
Global.asax:
routes.Add( "DomainRoute", new DomainRoute( "{subdomain}.yoururl.com", // Domain with parameters "{controller}/{action}", // URL with parameters new { controller = "Home", action = "Index", subdomain = UrlParameter.Optional } // Parameter defaults ));
http://subdomain.app.com will then add the parameter, 'subdomain' to your RouteValueDictionary.
Also, be sure to create a wildcard DNS record.