ASP.NET MVC Route 自訂限制條件(constraints)的技巧
- 2013-06-15
- 30543
- 0
- ASP.NET MVC Routes 技巧
在 demo 講 ASP.NET MVC 的課程時,我都會很早就提到網址路由(Route) 的部分,不過只是簡單的說一下預設的對應方式,因為不會自訂網址路由(Route)並不會影響整個網站的開發,頂多就是做出來以後網址有點醜而已....
但是當開發者已經進階到開始要改網址路由(Route)時,就會發現這裡面學問還真多,而本篇不是要詳細的說明基本的部分(基本說明請參考《ASP.NET MVC4網站開發美學》),只是要介紹如何藉由實做 IRouteConstraint 來自訂約束(constraints)條件,當你學會了自訂約束條件後,網址路由設定的難度就會相對簡單很多。
現在 demo 開發網站幾乎都用 GUID 來做網站的唯一識別碼,以下拿最近利用空暇時間開發的 Best Gallery 網站來做示範。因為使用了 GUID 所以在預設的情況下網址應該都會長這樣
http://vs.demo.tc/Gallery/Index/9f819024-3fea-43d5-afdd-e8c2ccad73b7
這種網址就是前文提到的「醜」,中間多了一個很無意義的 Index 因此必須做點改變。
本範例使用 ASP.NET MVC4
開啟 App_Start 資料夾內的 RouteConfig.cs 可以看到預設路由
routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } )
想拿掉 Index 這個累贅,可以使用下面這段路由定義。
routes.MapRoute( name: "Gallery", url: "Gallery/{id}", defaults: new {controller = "Gallery", action = "Index", id = UrlParameter.Optional}, constraints:new{id =@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"}, );
因為要避免太容易匹配成功,所以設計路由時會利用 Constraints 來增加條件約束,本範例約束了 id 這個參數必須要符合 GUID 的格式,而 GUID 的格式使用了 Regex 來描述。
網址路由為由上而下匹配,匹配到就離開,因此請把新增的路由定義放在第一個位置。
好!這樣子事情就解決了,現在可以看到自動產出的網址變成
http://vs.demo.tc/Gallery/9f819024-3fea-43d5-afdd-e8c2ccad73b7
需求達成了,本文就結束了嗎?當然不是囉,各位讀者可以看到上面使用了 Regex 描述了 GUID 的格式,但是對於初學者來說 Regex 是屬於天書等級的存在,雖然 demo 一直強調看懂 Regex 是一個很重要的事情,但是這次讓我們改用更好更正規的作法,先拋棄 Regex 吧。
在 MVC 中允許我們利用實做 IRouteConstraint 這個介面來設計自訂的路由約束條件,現在就新增一個吧
public class GuidConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (values.ContainsKey(parameterName)) { var guid = values[parameterName] as Guid?; if (guid.HasValue == false) { var stringValue = values[parameterName] as string; if (string.IsNullOrWhiteSpace(stringValue)==false) { Guid parsedGuid; // .NET 4 新增的 Guid.TryParse Guid.TryParse(stringValue, out parsedGuid); guid = parsedGuid; } } return (guid.HasValue && guid.Value != Guid.Empty); } return false; } }
以上的 Code 可以直接複製貼上使用(因為最近有人一直噹我,說我 Blog 的 Code 都不能複製貼上,所以特別放了一個可以複製貼上就完成的 Code)
接下來去改掉剛剛的路由定義,讓它變成這樣
routes.MapRoute( name: "Gallery", url: "Gallery/{id}", defaults: new { controller = "Gallery", action = "Index", id = UrlParameter.Optional }, constraints: new { id = new GuidConstraint() } );
漂亮吧,天書的部分完全拿掉了,而且因為命名正確,所以任何人一看就知道 id 被約束成 GUID
藉由自訂路由的約束定義,可以讓開發者在定義路由的時候更隨心所欲,因為你現在可以寫程式去做驗證,那絕對是好寫很多,就算你想要限制只有 Home 和 Account 的 Controller 才吃這路由也可以輕鬆的寫
public class OnlyHomeAndAccountConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { string[] constraintName = new string[] { "Home", "Account" }; if (values.ContainsKey(parameterName)) { var stringValue = values[parameterName] as string; //不考慮大小寫的比對方式 return Array.Exists(constraintName, val => val.Equals(stringValue, StringComparison.InvariantCultureIgnoreCase)); } return false; } }
甚至是經常會用到的日期也是沒問題的
public class DateTimeConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { var dateTime = values[parameterName] as DateTime?; if (dateTime.HasValue == false) { var stringValue = values[parameterName] as string; if (string.IsNullOrWhiteSpace(stringValue) == false) { DateTime parsedDateTime; DateTime.TryParse(stringValue, out parsedDateTime); dateTime = parsedDateTime; } } return (dateTime.HasValue && dateTime.Value != default(DateTime)); } }
因為被人噹所以這篇一次放上了三個可以直接複製貼上的code,希望下次 twMVC 每週四固定聚會不會在被噹了
回應討論