I am currently working on a sideprojectside-project and have the basic foundation and architecture I want in place for the site. I wanted to get your opinion and thoughts on my current practices and methods before I get too deep down the rabbit hole and any potential issues would be difficult to remove.
A Custom 'SubdomainRoute'custom
SubdomainRoute
inheriting 'Route' is created that takes an additional parameter 'sub'. It then overrides GetRouteDataGetRouteData
and compares the current subdomain (either the subdomain, or a '?sub=blah' query param) against the routes specified subdomain.using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace Teknik { public class SubdomainRoute : Route { public string Subdomain { get; set; } public SubdomainRoute(string subdomain, string url, IRouteHandler handler) : base(url, handler) { this.Subdomain = subdomain; } public SubdomainRoute(string subdomain, string url, RouteValueDictionary defaults, IRouteHandler handler) : base(url, defaults, handler) { this.Subdomain = subdomain; } public SubdomainRoute(string subdomain, string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler handler) : base(url, defaults, constraints, handler) { this.Subdomain = subdomain; } public SubdomainRoute(string subdomain, string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler handler) : base(url, defaults, constraints, dataTokens, handler) { this.Subdomain = subdomain; } public override RouteData GetRouteData(HttpContextBase httpContext) { var routeData = base.GetRouteData(httpContext); if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place. string subdomain = httpContext.Request.QueryString["sub"]; // A subdomain specified as a query parameter takes precedence over the hostname. if (subdomain == null) { string host = httpContext.Request.Headers["Host"]; subdomain = host.GetSubdomain(); } else { if (routeData.Values["sub"] == null) { routeData.Values["sub"] = subdomain; } else { subdomain = routeData.Values["sub"].ToString(); } } //routeData.Values["sub"] = subdomain; if (Subdomain == "*" || Subdomain == subdomain) { return routeData; } return null; } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { object subdomainParam = requestContext.HttpContext.Request.QueryString["sub"]; if (subdomainParam != null && values["sub"] == null) values["sub"] = subdomainParam; return base.GetVirtualPath(requestContext, values); // we now have the route based on subdomain } } }
The routes are added practically the same as the normal route, with the addition of the sub param. I have one subdomain set aside for the dev site ('dev'), which is basically a copy of the routing of the normal subdomain with the Area tacked on as the first url section.
public override void RegisterArea(AreaRegistrationContext context) { context.MapSubdomainRoute( "About.Index", // Route name "dev", // Subdomain "About", // URL with parameters new { controller = "About", action = "Index" }, // Parameter defaults new[] { typeof(Controllers.AboutController).Namespace } ); context.MapSubdomainRoute( "About.Index", // Route name "about", // Subdomain "", // URL with parameters new { controller = "About", action = "Index" }, // Parameter defaults new[] { typeof(Controllers.AboutController).Namespace } ); }
To generate a link using the subdomain routing, I created a UrlHelper
UrlHelper
extension that will take the subdomain you want, the route name, and the route values you want to use. By specifying the route name, and defining a common naming convention for the names, I don't have to deal with parsing the route table to find a valid route, since I know exactly which route I want to use. This extension will then attach the correct subdomain or query param depending on the subdomain provided and the current subdomain of the current page.using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Web.UI; namespace Teknik { public static class UrlExtensions { public static string SubRouteUrl(this UrlHelper url, string sub, string routeName) { return url.SubRouteUrl(sub, routeName, null); } /// <summary> /// Generates a full URL given the specified sub domain and route name /// </summary> /// <param name="url"></param> /// <param name="sub"></param> /// <param name="routeName"></param> /// <param name="routeValues"></param> /// <returns></returns> public static string SubRouteUrl(this UrlHelper url, string sub, string routeName, object routeValues) { string host = url.RequestContext.HttpContext.Request.Url.Authority; string domain = host; string rightUrl = string.Empty; // get current subdomain string curSub = host.GetSubdomain(); var split = host.Split('.'); // split the host by '.' if (split.Count() > 2) { int index = host.IndexOf('.') + 1; if (index >= 0 && index < host.Length) domain = host.Substring(index, (host.Length - index)); } // Grab the sub from parameters if it exists string subParam = url.RequestContext.HttpContext.Request.QueryString["sub"]; // A subdomain specified as a query parameter takes precedence over the hostname. // If the param is not being used, we will use the curSub if (string.IsNullOrEmpty(subParam)) { string firstSub = (curSub == "dev") ? "dev" : sub; if (!string.IsNullOrEmpty(firstSub)) { routeName = firstSub + "." + routeName; domain = firstSub + "." + domain; } } else { string desiredSub = (subParam == "dev") ? "dev" : sub; routeName = desiredSub + "." + routeName; domain = host; } try { rightUrl = url.RouteUrl(routeName, routeValues); } catch (ArgumentException) { } string absoluteAction = string.Format("{0}://{1}{2}", url.RequestContext.HttpContext.Request.Url.Scheme, domain, rightUrl); if (!string.IsNullOrEmpty(subParam) && subParam != "dev") { absoluteAction = absoluteAction.SetUrlParameter("sub", sub); } return absoluteAction; } public static string GetUrlParameters(this string url) { Uri uri = new Uri(url); var queryParts = HttpUtility.ParseQueryString(uri.Query); return queryParts.ToString(); } public static string SetUrlParameter(this string url, string paramName, string value) { return new Uri(url).SetParameter(paramName, value).ToString(); } public static Uri SetParameter(this Uri url, string paramName, string value) { var queryParts = HttpUtility.ParseQueryString(url.Query); queryParts[paramName] = value; return new Uri(url.AbsoluteUriExcludingQuery() + '?' + queryParts.ToString()); } public static string AbsoluteUriExcludingQuery(this Uri url) { return url.AbsoluteUri.Split('?').FirstOrDefault() ?? String.Empty; } public static string GetSubdomain(this string host) { if (host.IndexOf(":") >= 0) host = host.Substring(0, host.IndexOf(":")); Regex tldRegex = new Regex(@"\.[a-z]{2,3}\.[a-z]{2}$"); host = tldRegex.Replace(host, ""); tldRegex = new Regex(@"\.[a-z]{2,4}$"); host = tldRegex.Replace(host, ""); if (host.Split('.').Length > 1) return host.Substring(0, host.IndexOf(".")); else return string.Empty; } } }
The source can be found here: http://git.teknik.io/Teknikode/TeknikASP
YouThe source can view a live demo here:be found https://dev.teknik.io/here.
Thanks in advance for any thoughts or concernsYou can view a live demo here.