先給出本文中測試用的 controller:html
public class PersonsController : Controller { public ActionResult Query(string name) { return View(); } }
不嚴格來說,ASP.NET MVC 對 Url 是不敏感的,如下 Url 都是相同的,均可以訪問到 PersonController 的 Query 方法:瀏覽器
但對 MVC 的數據綁定來講,大小寫彷佛仍是有區別的:mvc
對以上兩個 Url,Query 中 name 參數會接收到兩個不一樣值:Bob、bob。Action 中的參數只是原樣接收,並無做任何處理。至於name 字符串的大小寫是否敏感要看具體的應用了。框架
再回頭看前面的三個 Url:ide
本文探討如何在 MVC 中使用第三種方式,也就是小寫(但不徹底小寫),目標以下:函數
在不影響程序正常運行的前提下,將全部能小寫的都小寫,如:測試
~/persons/query?name=Bob&age=18ui
~/persons/query/name/Bob/age/18this
在 View 中生成超級連接有多種方式:url
<%: Html.ActionLink("人員查詢", "Query", "Persons", new { name = "Bob" }, null) %> <%: Html.RouteLink("人員查詢", new { controller = "Persons", action = "Query", name = "Bob" })%> <a href="<%:Url.Action("Query", "Persons", new { name="Bob" }) %>">人員查詢</a>
在 Action 中,可使用 RedirectTo 來調轉至新的頁面:
return RedirectToAction("Query", "Persons", new { name = "Bob" }); return RedirectToRoute(new { controller = "Persons", action = "Query", name = "Bob" });
ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 都會生成 Url,並最終顯示在瀏覽器的地址欄中。
這四個方法都有不少重載,想從這裏下手控制 Url 小寫實在是太麻煩了。固然也不可行,由於可能還有其它方式來生成 Url。
MVC 是一個很是優秀的框架,但凡優秀的框架都會遵循 DRY(Don't repeat yourself) 原則,MVC 也不例外。MVC 中 RouteBase 負責 Url 的解析和生成:
public abstract class RouteBase { public abstract RouteData GetRouteData(HttpContextBase httpContext); public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); }
GetRouteData 用來解析 Url,GetVirtualPath 用來生成 Url。ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 內部都會調用 GetVirtualPath 方法來生成 Url。
所以咱們的入手點就是 GetVirtualPath 方法。
MVC 中 RouteBase 的具體實現類是 Route,咱們常常在 Global.asax 中常常使用:
public class MvcApplication : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } //... }
MapRoute 返回 Route,MapRoute 有不少重載,用來簡化咱們構建 Route 的過程。
Route 類沒有給咱們提供可直接擴展的地方,所以咱們只能自定義一個新的 Route 來實現咱們的小寫 Url。但處理路由的細節也是至關麻煩的,所以咱們最簡單的方式就是寫一個繼承自 Route 的類,而後重寫它的 GetVirtualPath 方法:
public class LowerCaseUrlRoute : Route { public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { //在此處進行小寫處理 return base.GetVirtualPath(requestContext, values); } }
再來看下咱們的目標:
~/persons/query?name=Bob&age=18
~/persons/query/name/Bob/age/18
其實咱們只須要進行兩步操做:
那咱們先來完成這個功能吧:
private static readonly string[] requiredKeys = new [] { "area", "controller", "action" }; private void LowerRouteValues(RouteValueDictionary values) { foreach (var key in requiredKeys) { if (values.ContainsKey(key) == false) continue; var value = values[key]; if (value == null) continue; var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); if (valueString == null) continue; values[key] = valueString.ToLower(); } var otherKyes = values.Keys .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase) .ToArray(); foreach (var key in otherKyes) { var value = values[key]; values.Remove(key); values.Add(key.ToLower(), value); } }
GetVirtualPath 生成 Url 時,會將 requestContext.RouteData.Values、values(第二個參數) 以及 Defaults(當前 Router 的默認值)三個 RouteValueDictionary 進行合併,如在 View 寫了以下的一個 ActionLinks:
<%: Html.ActionLink("查看") %>
生成的 Html 代碼多是:
<a href="/Home/Details">查看</a>
由於沒有指定 Controller,MVC 會自動使用當前的,即從 requestContext.RouteData.Values 中獲取 Controller,獲得 」Home「;」Details「來自 values;若是連 ActionLink 中 Action 也不指定,那將會從 Defaults 中取值。
所以咱們必須將這三個 RouteValueDictionary 都進行處理才能達到咱們的目標:
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { LowerRouteValues(requestContext.RouteData.Values); LowerRouteValues(values); LowerRouteValues(Defaults); return base.GetVirtualPath(requestContext, values); }
再加上幾個構造函數,完整的 LowerCaseUrlRoute 以下:
public class LowerCaseUrlRoute : Route { private static readonly string[] requiredKeys = new [] { "area", "controller", "action" }; public LowerCaseUrlRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler) { } public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler){ } public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler) { } public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler) { } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { LowerRouteValues(requestContext.RouteData.Values); LowerRouteValues(values); LowerRouteValues(Defaults); return base.GetVirtualPath(requestContext, values); } private void LowerRouteValues(RouteValueDictionary values) { foreach (var key in requiredKeys) { if (values.ContainsKey(key) == false) continue; var value = values[key]; if (value == null) continue; var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); if (valueString == null) continue; values[key] = valueString.ToLower(); } var otherKyes = values.Keys .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase) .ToArray(); foreach (var key in otherKyes) { var value = values[key]; values.Remove(key); values.Add(key.ToLower(), value); } } }
有了 LowerCaseUrlRoute,咱們就能夠修改 Global.asax 文件中的路由了。
這一步不是必須的,但有了這個 MapHelper 咱們在修改 Global.asax 文件中的路由時能夠很是方便:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapLowerCaseUrlRoute( //routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
尤爲是已經配置了不少路由的狀況下,其代碼以下:
public static class LowerCaseUrlRouteMapHelper { public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url){ return routes.MapLowerCaseUrlRoute(name, url, null, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults){ return routes.MapLowerCaseUrlRoute(name, url, defaults, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, string[] namespaces){ return routes.MapLowerCaseUrlRoute(name, url, null, null, namespaces); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints){ return routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces){ return routes.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces){ if (routes == null) throw new ArgumentNullException("routes"); if (url == null) throw new ArgumentNullException("url"); LowerCaseUrlRoute route2 = new LowerCaseUrlRoute(url, new MvcRouteHandler()); route2.Defaults = new RouteValueDictionary(defaults); route2.Constraints = new RouteValueDictionary(constraints); route2.DataTokens = new RouteValueDictionary(); LowerCaseUrlRoute item = route2; if ((namespaces != null) && (namespaces.Length > 0)) item.DataTokens["Namespaces"] = namespaces; routes.Add(name, item); return item; } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url){ return context.MapLowerCaseUrlRoute(name, url, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults){ return context.MapLowerCaseUrlRoute(name, url, defaults, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, string[] namespaces){ return context.MapLowerCaseUrlRoute(name, url, null, namespaces); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints) { return context.MapLowerCaseUrlRoute(name, url, defaults, constraints, null); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, string[] namespaces){ return context.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces); } public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints, string[] namespaces) { if ((namespaces == null) && (context.Namespaces != null)) namespaces = context.Namespaces.ToArray<string>(); LowerCaseUrlRoute route = context.Routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, namespaces); route.DataTokens["area"] = context.AreaName; bool flag = (namespaces == null) || (namespaces.Length == 0); route.DataTokens["UseNamespaceFallback"] = flag; return route; } }
大功告成,若是你感興趣,不妨嘗試下!
寫到這裏吧,若是須要,請下載本文中的示例代碼:MvcLowerCaseUrlRouteDemo.rar(209KB)
若是你有其它辦法,歡迎交流!
轉載請註明本文地址:
http://www.cnblogs.com/ldp615/archive/2010/11/10/asp-net-mvc-lower-case-url-route.html