MVC项目不同域之间的UrlRouting
一、DomainAction,方便生成不同域下的url
1、新建3个MVC项目,一个公用类库WebCore
Demo.WebApplication0
绑定域名 www.demo.com demo.com t0.demo.com
Demo.WebApplication1(可不用建)
绑定域名 t1.demo.com
Demo.WebApplication2(可不用建)
绑定域名 t2.demo.com
Demo.WebCore
2、项目Demo.WebApplication0新增一个学生的Controller
1 public class StudentController : Controller 2 { 3 public ActionResult Search(int gender, int degree, int pageSize, int page) 4 { 5 return Content(string.Format("查找学生,筛选条件。性别:{0},学历:{1},每页学生数量:{2},页码:{3}" 6 , (gender == 1 ? "男" : (gender == 2 ? "女" : "不限")) 7 ,degree, pageSize, page 8 )); 9 //return View(); 10 } 11 12 13 }
3、域名枚举
1 public enum DemoDomain 2 { 3 /// <summary> 4 /// www.demo.com 5 /// demo.com 6 /// t0.demo.com 7 /// </summary> 8 T0, 9 10 /// <summary> 11 /// t1.demo.com 12 /// </summary> 13 T1, 14 15 /// <summary> 16 /// t2.demo.com 17 /// </summary> 18 T2, 19 20 /// <summary> 21 /// demo.com 22 /// </summary> 23 Default, 24 25 }
4、System.Web.Mvc.UrlHelper的静态扩展类DemoUrlExtensions
1 public static class DemoUrlExtensions 2 { 3 public static string DomainAction(this UrlHelper urlHelper, string actionName, string controllerName) 4 { 5 return urlHelper.DomainAction(actionName, controllerName, DemoDomain.Default); 6 } 7 8 public static string DomainAction(this UrlHelper urlHelper, string actionName, string controllerName, object routeValues) 9 { 10 return urlHelper.DomainAction(actionName, controllerName, routeValues, DemoDomain.Default); 11 } 12 13 public static string DomainAction(this UrlHelper urlHelper, string actionName, string controllerName, DemoDomain Domain) 14 { 15 return urlHelper.DomainAction(actionName, controllerName, new { }, Domain); 16 } 17 18 public static string DomainAction(this UrlHelper urlHelper, string actionName, string controllerName, object routeValues, DemoDomain Domain) 19 { 20 return DomainAction(urlHelper, actionName, controllerName, new RouteValueDictionary(routeValues), Domain); 21 } 22 23 public static string DomainAction(this UrlHelper urlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, DemoDomain Domain) 24 { 25 string subDomain = string.Empty; 26 string HostName = string.Empty; 27 switch (Domain) 28 { 29 case DemoDomain.Default: 30 subDomain = string.Empty; 31 break; 32 case DemoDomain.T0: 33 subDomain = "t0"; 34 break; 35 case DemoDomain.T1: 36 subDomain = "t1"; 37 break; 38 case DemoDomain.T2: 39 subDomain = "t2"; 40 break; 41 default: 42 subDomain = string.Empty; 43 break; 44 } 45 46 if (!string.IsNullOrEmpty(subDomain)) 47 { 48 HostName = subDomain + ".demo.com"; 49 } 50 else 51 { 52 HostName = "www.demo.com"; 53 } 54 //核心的一行 55 string url = urlHelper.Action(actionName, controllerName, routeValues, "http", HostName); 56 if (url.Contains('?')) 57 { 58 url = url.TrimEnd('/'); 59 } 60 else 61 { 62 if (url.EndsWith(".html/")) 63 { 64 url = url.TrimEnd('/'); 65 } 66 } 67 return url; 68 } 69 70 }
5、修改Home/Index内容
1 <div> 2 <a href="@Url.DomainAction("Index","Home", DemoDomain.T1)">我要去t1.demo.com</a> 3 <a href="@Url.DomainAction("Index","Home", DemoDomain.T2)">我要去t2.demo.com</a> 4 <a href="@Url.DomainAction("Index", "Home", new { from = "www" }, DemoDomain.T2)">我要去t2.demo.com,我要带参数,表示我是从www过来的</a> 5 6 <a href="@Url.DomainAction("Search", "Student", new { gender = 1, degree = 0, pageSize = 20, page = 1 }, DemoDomain.T0)">查找性别男的学生</a> 7 8 </div>
对应的html源码
1 <div> 2 <a href="http://t1.demo.com/">我要去t1.demo.com</a> 3 <a href="http://t2.demo.com/">我要去t2.demo.com</a> 4 <a href="http://t2.demo.com/?from=www">我要去t2.demo.com,我要带参数,表示我是从www过来的</a> 5 6 <a href="http://t0.demo.com/Student/Search?gender=1&degree=0&pageSize=20&page=1">查找性别男的学生</a> 7 8 </div>
二、注册路由,SEO优化
现在已经能做到
@Url.DomainAction("Search", "Student", new { gender = 1, degree = 0, pageSize = 20, page = 1 }, DemoDomain.T0)
对应的Url为
http://t0.demo.com/Student/Search?gender=1°ree=0&pageSize=20&page=1
,但这个链接不利于SEO,理想的情况可能是这样(请把t0当成student)
http://t0.demo.com/Search/1-0-20-1.html
定义路由
1 public class DomainRoute : Route 2 { 3 private static 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_]*})*-?\.?\/?", RegexOptions.Compiled); 4 private static Dictionary<string, Regex> domainRegDic = new Dictionary<string, Regex>(); 5 private static Dictionary<string, Regex> pathRegDic = new Dictionary<string, Regex>(); 6 7 /// <summary> 8 /// 9 /// </summary> 10 public Regex PathRegex 11 { 12 get 13 { 14 if (!pathRegDic.Keys.Contains(Url)) 15 { 16 switch (Url) 17 { 18 case WebCoreConsts.URL_T0_SearchStudent: 19 pathRegDic[Url] = new Regex(ConvertToPattern("search/{gender}-{degree}-{pagesize}-{page}.html"), RegexOptions.Compiled | RegexOptions.IgnoreCase); 20 break; 21 default: 22 pathRegDic[Url] = CreateRegex(Url); 23 break; 24 } 25 } 26 return pathRegDic[Url]; 27 } 28 } 29 30 /// <summary> 31 /// 32 /// </summary> 33 public Regex DomainRegex 34 { 35 get 36 { 37 if (!domainRegDic.Keys.Contains(Domain)) 38 { 39 40 domainRegDic[Domain] = CreateRegex(Domain); 41 } 42 return domainRegDic[Domain]; 43 } 44 45 } 46 47 public string Domain { get; set; } 48 49 public DomainRoute(string domain, string url, RouteValueDictionary defaults) 50 : base(url, defaults, new MvcRouteHandler()) 51 { 52 Domain = domain; 53 } 54 55 public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler) 56 : base(url, defaults, routeHandler) 57 { 58 Domain = domain; 59 } 60 61 public DomainRoute(string domain, string url, object defaults) 62 : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) 63 { 64 Domain = domain; 65 } 66 67 //new 68 public DomainRoute(string domain, string url, object defaults, object constraints) 69 : base(url, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new MvcRouteHandler()) 70 { 71 Domain = domain; 72 } 73 74 public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler) 75 : base(url, new RouteValueDictionary(defaults), routeHandler) 76 { 77 Domain = domain; 78 } 79 80 public override RouteData GetRouteData(HttpContextBase httpContext) 81 { 82 // 请求信息 83 string requestDomain = httpContext.Request.Headers["host"]; 84 if (!string.IsNullOrEmpty(requestDomain)) 85 { 86 if (requestDomain.IndexOf(":") > 0) 87 { 88 requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":")); 89 } 90 } 91 else 92 { 93 requestDomain = httpContext.Request.Url.Host; 94 } 95 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; 96 97 // 匹配域名和路由 98 Match domainMatch = DomainRegex.Match(requestDomain); 99 Match pathMatch = PathRegex.Match(requestPath); 100 101 // 路由数据 102 RouteData data = null; 103 if (domainMatch.Success && pathMatch.Success) 104 { 105 data = new RouteData(this, RouteHandler); 106 107 // 添加默认选项 108 if (Defaults != null) 109 { 110 foreach (KeyValuePair<string, object> item in Defaults) 111 { 112 data.Values[item.Key] = item.Value; 113 } 114 } 115 116 // 匹配域名路由 117 for (int i = 1; i < domainMatch.Groups.Count; i++) 118 { 119 Group group = domainMatch.Groups[i]; 120 if (group.Success) 121 { 122 string key = DomainRegex.GroupNameFromNumber(i); 123 124 if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) 125 { 126 if (!string.IsNullOrEmpty(group.Value)) 127 { 128 data.Values[key] = group.Value; 129 } 130 } 131 } 132 } 133 134 // 匹配域名路径 135 for (int i = 1; i < pathMatch.Groups.Count; i++) 136 { 137 Group group = pathMatch.Groups[i]; 138 if (group.Success) 139 { 140 string key = PathRegex.GroupNameFromNumber(i); 141 142 if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) 143 { 144 if (!string.IsNullOrEmpty(group.Value)) 145 { 146 data.Values[key] = group.Value; 147 } 148 } 149 } 150 } 151 } 152 153 return data; 154 } 155 156 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 157 { 158 159 VirtualPathData pathData = base.GetVirtualPath(requestContext, RemoveDomainTokens(values)); 160 if (pathData != null && !string.IsNullOrEmpty(pathData.VirtualPath)) 161 { 162 if (pathData.VirtualPath.EndsWith(".html/")) 163 { 164 pathData.VirtualPath = pathData.VirtualPath.TrimEnd('/'); 165 } 166 else if (!pathData.VirtualPath.EndsWith("/") && !pathData.VirtualPath.EndsWith(".html")) 167 { 168 pathData.VirtualPath = pathData.VirtualPath + "/"; 169 } 170 } 171 return pathData; 172 } 173 174 private Regex CreateRegex(string source) 175 { 176 try 177 { 178 string src = source; 179 // 替换 180 source = source.Replace("/", @"\/?"); 181 source = source.Replace(".", @"\.?"); 182 source = source.Replace("-", @"\-?"); 183 source = source.Replace("{", @"(?<"); 184 source = source.Replace("}", @">([a-zA-Z0-9_]*))"); 185 return new Regex("^" + source + "$", RegexOptions.Compiled); 186 } 187 catch (Exception ex) 188 { 189 //AppLogger.Error("创建正则出错:" + source + " 详情:" + ex.Message); 190 } 191 return new Regex("^" + source + "$", RegexOptions.Compiled); 192 } 193 194 private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values) 195 { 196 197 Match tokenMatch = tokenRegex.Match(Domain); 198 for (int i = 0; i < tokenMatch.Groups.Count; i++) 199 { 200 Group group = tokenMatch.Groups[i]; 201 if (group.Success) 202 { 203 string key = group.Value.Replace("{", "").Replace("}", ""); 204 if (values.ContainsKey(key)) 205 values.Remove(key); 206 } 207 } 208 209 return values; 210 } 211 212 private static string ConvertToPattern(string source) 213 { 214 try 215 { 216 string src = source; 217 // 替换 218 source = source.Replace("/", @"\/?"); 219 source = source.Replace(".", @"\.?"); 220 source = source.Replace("-", @"\-?"); 221 source = source.Replace("{", @"(?<"); 222 source = source.Replace("}", @">([a-zA-Z0-9_]*))"); 223 return "^" + source + "$"; 224 } 225 catch (Exception ex) 226 { 227 } 228 return source; 229 } 230 231 }
注册路由
1 routes.LowercaseUrls = true; 2 3 routes.Add("T0StudentSearchDomainRoute", new DomainRoute( 4 "t0.demo.com", // Domain with parameters 5 WebCoreConsts.URL_T0_SearchStudent, // URL with parameters 6 new { controller = "Student", action = "Search" } 7 //, new { } 8 ));
可能还需要在web.config的system.webServer项中增加一行配置,否则.html不会进入路由,会直接映射成本地路径
<modules runAllManagedModulesForAllRequests="true" />
这时候
<a href="@Url.DomainAction("Search", "Student", new { gender = 1, degree = 0, pageSize = 20, page = 1 }, DemoDomain.T0)" target="_blank">查找性别男的学生</a>
就会对应成
<a href="http://t0.demo.com/search/1-0-20-1.html" target="_blank">查找性别男的学生</a>
附:源码下载