動態二級域名的實現:
應用場景:目前產品要實現SaaS功能,因為工作需要實現二級域名:www.{CompanyUrl}.xxx.com
假設產品主域名入口為:www.xxx.com
當a公司租戶登錄時:www.a.xxx.com
當b公司租戶登錄時: www.b.xxx.com
首先想到的是對Url的重寫:(網上有關於UrlRewrite的實現。在ASP.NET中這也是常用的手法。)
Route簡介:ASP.NET路由可以不用映射到網站特定文件的URL.由於該 URL 不必映射到文件,因此可以使用對用戶操作進行描述因而更易於被用戶理解的 URL。.NET Framework 3.5 SP1已經包含了ASP.NET Routing引擎。現在微軟已經在ASP.NET WebForms 4.0中增加了對Routing引擎更好的支持,它使用表達式構造器進行雙向Routing。
MVC 應用程序中的典型 URL 模式——來自MSDN
MVC 應用程序中用於路由的 URL 模式通常包括 {controller} 和 {action} 占位符。
當收到請求時,會將其路由到 UrlRoutingModule 對象,然后路由到 MvcHandler HTTP 處理程序。 MvcHandler HTTP 處理程序通過向 URL 中的控制器值添加后綴“Controller”以確定將處理請求的控制器的類型名稱,來確定要調用的控制器。URL 中的操作值確定要調用的操作方法。
MVC項目中添加路由,Global.asax 文件默認的MVC 路由的代碼。
默認配置:
View Code
1 public static void RegisterRoutes(RouteCollection routes) 2 { 3 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 4 5 routes.MapRoute( 6 "Default", // Route name 7 "{controller}/{action}/{id}", // URL with parameters 8 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults 9 ); 10 11 } 12 protected void Application_Start() 13 { 14 AreaRegistration.RegisterAllAreas(); 15 16 RegisterGlobalFilters(GlobalFilters.Filters); 17 RegisterRoutes(RouteTable.Routes); 18 }
涉及類參考
| 類 | 說明 |
| Route | 表示 Web 窗體或 MVC 應用程序中的路由。 |
| RouteBase | 用作表示 ASP.NET 路由的所有類的基類。 |
| RouteTable | 存儲應用程序的路由。 |
| RouteData | 包含所請求路由的值。 |
| RequestContext | 包含有關對應於路由的 HTTP 請求的信息。 |
| RouteValueDictionary | 提供用於存儲路由 Constraints、Defaults 和 DataTokens 對象的方法。 |
| VirtualPathData | 提供用於從路由信息生成 URL 的方法。 |
因為目前采用的是ASP.NET MVC 3進而可以利用擴展Route的方式實現。
首先定義DomainData、DomainRoute類
代碼如下:
DomainRoute類:
View Code
1 public class DomainRoute : Route 2 { 3 private Regex domainRegex; 4 private Regex pathRegex; 5 6 public string Domain { get; set; } 7 8 public DomainRoute(string domain, string url, RouteValueDictionary defaults) 9 : base(url, defaults, new MvcRouteHandler()) 10 { 11 Domain = domain; 12 } 13 14 public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler) 15 : base(url, defaults, routeHandler) 16 { 17 Domain = domain; 18 } 19 20 public DomainRoute(string domain, string url, object defaults) 21 : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) 22 { 23 Domain = domain; 24 } 25 26 public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler) 27 : base(url, new RouteValueDictionary(defaults), routeHandler) 28 { 29 Domain = domain; 30 } 31 32 public override RouteData GetRouteData(HttpContextBase httpContext) 33 { 34 // 構造 regex 35 domainRegex = CreateRegex(Domain); 36 pathRegex = CreateRegex(Url); 37 38 // 請求信息 39 string requestDomain = httpContext.Request.Headers["host"]; 40 if (!string.IsNullOrEmpty(requestDomain)) 41 { 42 if (requestDomain.IndexOf(":") > 0) 43 { 44 requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":")); 45 } 46 } 47 else 48 { 49 requestDomain = httpContext.Request.Url.Host; 50 } 51 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 52 53 // 匹配域名和路由 54 Match domainMatch = domainRegex.Match(requestDomain); 55 Match pathMatch = pathRegex.Match(requestPath); 56 57 // 路由數據 58 RouteData data = null; 59 if (domainMatch.Success && pathMatch.Success) 60 { 61 data = new RouteData(this, RouteHandler); 62 63 // 添加默認選項 64 if (Defaults != null) 65 { 66 foreach (KeyValuePair<string, object> item in Defaults) 67 { 68 data.Values[item.Key] = item.Value; 69 } 70 } 71 72 // 匹配域名路由 73 for (int i = 1; i < domainMatch.Groups.Count; i++) 74 { 75 Group group = domainMatch.Groups[i]; 76 if (group.Success) 77 { 78 string key = domainRegex.GroupNameFromNumber(i); 79 80 if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) 81 { 82 if (!string.IsNullOrEmpty(group.Value)) 83 { 84 data.Values[key] = group.Value; 85 } 86 } 87 } 88 } 89 90 // 匹配域名路徑 91 for (int i = 1; i < pathMatch.Groups.Count; i++) 92 { 93 Group group = pathMatch.Groups[i]; 94 if (group.Success) 95 { 96 string key = pathRegex.GroupNameFromNumber(i); 97 98 if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) 99 { 100 if (!string.IsNullOrEmpty(group.Value)) 101 { 102 data.Values[key] = group.Value; 103 } 104 } 105 } 106 } 107 } 108 109 return data; 110 } 111 112 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 113 { 114 return base.GetVirtualPath(requestContext, RemoveDomainTokens(values)); 115 } 116 117 public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values) 118 { 119 // 獲得主機名 120 string hostname = Domain; 121 foreach (KeyValuePair<string, object> pair in values) 122 { 123 hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString()); 124 } 125 126 // Return 域名數據 127 return new DomainData 128 { 129 Protocol = "http", 130 HostName = hostname, 131 Fragment = "" 132 }; 133 } 134 135 private Regex CreateRegex(string source) 136 { 137 // 替換 138 source = source.Replace("/", @"\/?"); 139 source = source.Replace(".", @"\.?"); 140 source = source.Replace("-", @"\-?"); 141 source = source.Replace("{", @"(?<"); 142 source = source.Replace("}", @">([a-zA-Z0-9_]*))"); 143 144 return new Regex("^" + source + "$"); 145 } 146 147 private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values) 148 { 149 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_]*})*-?\.?\/?"); 150 Match tokenMatch = tokenRegex.Match(Domain); 151 for (int i = 0; i < tokenMatch.Groups.Count; i++) 152 { 153 Group group = tokenMatch.Groups[i]; 154 if (group.Success) 155 { 156 string key = group.Value.Replace("{", "").Replace("}", ""); 157 if (values.ContainsKey(key)) 158 values.Remove(key); 159 } 160 } 161 162 return values; 163 } 164 } 165 public class DomainData 166 { 167 public string Protocol { get; set; } 168 public string HostName { get; set; } 169 public string Fragment { get; set; } 170 }
DomainData 類:
View Code
1 public class DomainData 2 { 3 public string Protocol { get; set; } 4 public string HostName { get; set; } 5 public string Fragment { get; set; } 6 }
然后在Global中配置路由
1: routes.Add("DomainRoute", new DomainRoute(
2: "www.{companyUrl}.wenthink.com", // Domain with parameters
3: "{controller}/{action}/{id}", // URL with parameters
4: new { companyUrl= "", controller = "Home", action = "Login", id = "" } // Parameter defaults
5: ));
效果圖:
a 公司用戶登錄時:
b公司用戶登錄時:
當用戶嘗試錯誤時:可自定義提示頁面
基礎支持:域名支持泛解析
然后要做的是:配置DNS服務,也就是讓你的域名支持泛解析
(Windows Server 才會有,其他的Windows系統可以嘗試修改Host文件,便於測試)
Step By Step
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
創建反向區域![]() |
![]() |
![]() |
| 綁定指針 | |
![]() |
![]() |
完成上面的操作,基本可以實現DNS的泛解析了.當然如果沒有綁定域名的話,只能修改Host文件來進行操作;
本機測試的情況下需要把Host文件中添加當前IP地址 所映射的域名,如本文中的wenthink.com
備注:以上解決了域名訪問的問題,但是會存在Session跨域訪問的丟失的現象.本例中采用了CrossDomainCookie的方式解決.當然不是唯一的解決方案. 方法很簡單,這里就不多說明了,需要的可以自己Google一下 :-)
參考資料:http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx
http://www.cnblogs.com/cyq1162/archive/2010/10/15/1851773.html
以上為個人學習摘要,如有錯誤,歡迎指正!!
下期:ASP.NET管道機制及IIS的工作原理淺析.
上個草圖先 :-P





















