介紹
不知道大家在使用 ASP.NET MVC 時有沒有一些擴展要求,反正我是有很多。在使用 MVC 這幾年(PS:我是從 1.0 開始學,2.0、3.0 開發至今),我深深地覺得 MVC 的擴展性真是太好了,幾乎你大部分的“合理”需求,用 MVC 都能實現。好了,廢話不多說了,今天我就實戰演示如何擴展 ASP.NET Route,希望能幫助到你。
小寫 URL
我想很多朋友和我一樣,使用 ASP.NET MVC 時都想要小寫的 URL。一般除非你在開發時手動把 Controller、Action 的名字建成小寫,或者在 Action 方法上標記 ActionNameAttribute,否則如果不經過擴展,很難實現完全的小寫 URL。把 Controller、Action 的名字建成小寫不符合 C# 編碼規范,當然如果你忽略這個規范,我也沒有辦法。另一方面,在 Action 方法上標記 ActionNameAttribute 工作量又太大,又不利於統一維護。至於為什么要小寫的 URL,我想是一種習慣吧,仔細觀察各大網站,你就會發現,小寫的 URL 是主流。
個性 URL
實現完小寫 URL 后,我還有一個特殊的 URL 規則要求,就是生成 A 元素的 URL 時,把 Action 的名字中的下划線(_)替換成減號(-),並且服務器端 ASP.NET MVC 能處理這個請求。打一個比如:
public class HomeController : Controller { /// <summary> /// 添加產品信息,可以通過訪問 URL:/home/add-product-information /// </summary> /// <returns></returns> public ActionResult Add_Product_Information() { return View(); } }
上面的例子,大家注意到了嗎,我希望使用 @Url.Action("Add_Product_Information") 能生成 /home/add-Product-Information 格式的 URL,並且還能訪問。很多朋友可能想到了,以為通過簡單的在 Global.asax 中配置路由可以實現,經過本人的實測,是無法實現的,因為配置路由時,比如: "{controller}/{action}/{id}" 這里的 {action} 是作為一個整體,而無法再把它拆分。像這種就只能通過擴展 Route 來實現了。 作者(音樂讓我說)博客地址:http://music.cnblogs.com
開始實戰
1. 建立一個 LowercaseRoute,繼承 System.Web.Routing.Route 類
在這個 LowercaseRoute 類里,我們需要做的是添加和 Route 類相似的構造函數,並且重寫 GetVirtualPath 方法。關鍵代碼如下:
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { VirtualPathData virtualPath = base.GetVirtualPath(requestContext, values); // 由 MVC 默認生成的 URL,比如:/Home/Add_Product_Information if (virtualPath != null) { string virtualPathValue = virtualPath.VirtualPath; // 獲取比如:/Home/Add_Product_Information int length = virtualPathValue.LastIndexOf("?"); if (length == 0) return virtualPath; if (length > 0) { string url1 = virtualPathValue.Substring(0, length).ToLowerInvariant(); // 把 controller 和 action 小寫 string url2 = virtualPathValue.Substring(length); // 保留 ? 后的參數 virtualPath.VirtualPath = url1 + url2; // 連接,比如:/home/add-product-information return virtualPath; } virtualPath.VirtualPath = virtualPath.VirtualPath.ToLowerInvariant(); } return virtualPath; }
以上這個類會在當你在 View、Controller 中調用 Url.Action("Add_Product_Information")、Html.ActionLink("Add_Product_Information") 時調用。
2. 建立一個 UnderlineRoute,繼承 LowercaseRoute 類
上面那個 LowercaseRoute 類僅僅是實現 URL 小寫的需求,而現在這個 UnderlineRoute 才是在小寫 URL 的基礎上再實現個性化的 URL 需求(PS:就是本文開頭介紹的,要把 Action 的名稱中帶有下划線的替換成減號)。作者(音樂讓我說)博客地址:http://music.cnblogs.com
首先,依舊添加和 LowercaseRoute 類相似的構造函數,然后重寫 GetVirtualPath 方法。完成代碼如下:
public class UnderlineRoute : LowercaseRoute { public const string UNDERLINE_STRING = "_"; public const string SUBTRACTION_SIGN = "-"; public UnderlineRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler) { } public UnderlineRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler) { } public UnderlineRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler) { } public UnderlineRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler) { } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { string action = (string)values["action"]; if(action != null) { if (action.Contains(UNDERLINE_STRING)) { values["action"] = action.Replace(UNDERLINE_STRING, SUBTRACTION_SIGN); } } // 以上的這小塊代碼就是生成 URL 時,把 RequestContext 中的 RouteData 中的 action 參數給替換, // 把 action 的名字中帶有下划線(_)的替換成減號(-) return base.GetVirtualPath(requestContext, values); } /// <summary> /// 替換成下划線 /// </summary> /// <param name="requestContext"></param> public static void ReplaceUnderlineToSubtractionSignInRouteData(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } // 以上的這小塊代碼就是當處理請求時,把 RequestContext 中的 RouteData 中的 action 參數給替換回來。 // 把請求的路由中的 action 參數給替換回來,把 action 的名字中帶有減號(-)的替換成下划線(_) string actionName = (string)requestContext.RouteData.Values["action"]; if (actionName != null) { if (actionName.Contains(UnderlineRoute.SUBTRACTION_SIGN)) { requestContext.RouteData.Values["action"] = actionName.Replace(UnderlineRoute.SUBTRACTION_SIGN, UnderlineRoute.UNDERLINE_STRING); } } } }
3. 建立相應的 RouteHandler。
如果您建立的項目是 MVC,則建立 UnderlineMvcRouteHandler 類,繼承 MvcRouteHandler 類。(PS:WebForms 可以忽略)
如果您建立的項目是傳統的 WebForm,則建立 UnderlinePageRouteHandler 類,繼承 PageRouteHandler 類。(PS:MVC 可以忽略)
代碼如下:
public class UnderlineMvcRouteHandler : MvcRouteHandler { public UnderlineMvcRouteHandler() :base() { } public UnderlineMvcRouteHandler(IControllerFactory controllerFactory): base(controllerFactory) { } protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { UnderlineRoute.ReplaceUnderlineToSubtractionSignInRouteData(requestContext); // 調用 UnderlineRoute 類處理 return base.GetHttpHandler(requestContext); } } public class UnderlinePageRouteHandler : PageRouteHandler { public UnderlinePageRouteHandler(string virtualPath) : base(virtualPath) { } public UnderlinePageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess) : base(virtualPath, checkPhysicalUrlAccess) { } public override IHttpHandler GetHttpHandler(RequestContext requestContext) { UnderlineRoute.ReplaceUnderlineToSubtractionSignInRouteData(requestContext); // 調用 UnderlineRoute 類處理 return base.GetHttpHandler(requestContext); } }
4. 擴展 RouteCollection 類的 MapRoute 方法。
在配置路由時,默認是調用 MapRoute 方法,而這個方法默認是由 MvcRouteHandler(PS: WebForm 默認是 PageRouteHandler),所以為了讓我們配置的路由默認由 UnderlineMvcRouteHandler
(WebForm 默認是 UnderlinePageRouteHandler)來處理,我們再添加幾個和 MapRoute 相似的方法。
對於 MVC 項目,我們添加幾個名叫 MapRouteUnderline 的重載。
public static Route MapRouteUnderline(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"); } // 注意: // 1. 這里使用的 Route 是 UnderlineRoute // 2. 這里使用的 RouteHandler 是 UnderlineMvcRouteHandler UnderlineRoute item = new UnderlineRoute(url, new UnderlineMvcRouteHandler()) { Defaults = new RouteValueDictionary(defaults), Constraints = new RouteValueDictionary(constraints), DataTokens = new RouteValueDictionary(namespaces) }; if (namespaces != null && namespaces.Length > 0) { item.DataTokens["Namespaces"] = namespaces; } routes.Add(name, item); return item; }
對於 WebForms 項目,我們添加幾個名叫 MapRouteUnderline 的重載。
public static Route MapPageRouteUnderline(this RouteCollection routes, string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) { if (routes == null) { throw new ArgumentNullException("routes"); } if (routeUrl == null) { throw new ArgumentNullException("routeUrl"); } // 注意: // 1. 這里使用的 Route 是 UnderlineRoute // 2. 這里使用的 RouteHandler 是 UnderlinePageRouteHandler UnderlineRoute item = new UnderlineRoute(routeUrl, defaults, constraints, dataTokens, new UnderlinePageRouteHandler(physicalFile, checkPhysicalUrlAccess) ); routes.Add(routeName, item); return item; }
5. 在 Global.asax 中配置路由
這一步是關鍵,要讓我們上面自定義的 Route 和 RouteHandler 生效,就必須在配置路由時,調用我們自定義的 MapRouteUnderline 和 MapPageRouteUnderline 方法。
//作者(音樂讓我說)博客地址:http://music.cnblogs.com public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // 請注意:這里調用的是 MapPageRouteUnderline 擴展方法 routes.MapPageRouteUnderline("adminDefault", "{controller}/{action}", "~/admin/{action}.aspx", true, null, new RouteValueDictionary(new { controller = "admin" })); // 請注意:這里調用的是 MapRouteUnderline 擴展方法 routes.MapRouteUnderline( "Default", // 路由名稱 "{controller}/{action}/{id}", // 帶有參數的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值 ); }
截止到這里,關鍵代碼都已經給出了,接下來就是檢驗我們的代碼。
6. 建立測試用的 Action
在 HomeController 中建立一個名叫 Add_Product_Information 的 Action 方法。並建立 cshtml 視圖,視圖里面隨便敲幾句話就可以了。
//作者(音樂讓我說)博客地址:http://music.cnblogs.com public class HomeController : Controller { /// <summary> /// 添加產品信息,可以通過訪問 URL:/home/add-product-information /// </summary> /// <returns></returns> public ActionResult Add_Product_Information() { return View(); } }
7. 在項目的根目錄建立一個名叫 admin 的文件夾。
再在 admin 文件夾下建立 hello.aspx、add_product_information.aspx 至於里面的內容,隨便敲幾句話就可以了。
8. 在視圖中測試,生成 URL
<ul id="menu"> <li>@Html.ActionLink("主頁", "Index", "Home")</li> <li>@Html.ActionLink("關於", "About", "Home")</li> <li>@Html.ActionLink("添加產品信息", "Add_Product_Information", "Home")</li> <li> @Html.ActionLink("后台 - 歡迎頁", "Hello", "Admin") </li> <li> @Html.ActionLink("后台 - 添加產品信息", "Add_Product_Information", "Admin") </li> </ul>
9. 運行
解決方案截圖如下:
以上 MVC 生成的鏈接都小寫了,並且把下划線(_)也替換成了減號(-)。運行時,也正常的呈現出來!
本文只是起一個穿針引線的作用,無法要求讀者也有這樣的要求,僅僅為了演示,希望和大家一起進步,如有不妥,請多多包涵,並歡迎指出!
作者(音樂讓我說)博客地址:http://music.cnblogs.com
示例代碼下載:點我下載
如果您覺得本文不錯,麻煩點一下“推薦”,謝謝!
轉載請注明,謝謝!
謝謝瀏覽!
謝謝瀏覽!