ASP.NET的路由係統:路由映射
總的來說,我們可以通過RouteTable的靜態屬性Routes得到一個基於應用的全局路由表,通過上麵的介紹我們知道這是一個類型的RouteCollection的集合對象,我們可以通過調用它的MapPageRoute進行路由映射,即注冊URL模板與某個物理文件的匹配關係。路由注冊的核心就是在全局路由表中添加一個Route對象,該對象的絕大部分屬性都可以通過MapPageRoute方法的相關參數來指定。接下來我們通過實現演示的方式來說明路由注冊的一些細節問題。
目錄
一、變量默認值
二、約束
三、對現成文件的路由
四、注冊路由忽略地址
五、直接添加路由對象
我們已前麵介紹的關於獲取天氣預報信息的路由地址,我們在創建的ASP.NET Web應用中創建一個Weather.aspx頁麵,不過我們並不打算在該頁麵中呈現任何天氣信息,而是將基於該頁麵的路由信息打印出來。該頁麵主體部分的HTML如下所示,我們不僅將基於當前頁麵的RouteData對象的Route和RouteHandler屬性類型輸出來,還將存儲於Values和DataTokens字典的變量顯示出來。
1: <body>
2: <form id="form1" runat="server">
3: <div>
4: <table>
5: <tr>
6: <td>Route:</td>
7: <td><%=RouteData.Route != null? RouteData.Route.GetType().FullName:"" %></td>
8: </tr>
9: <tr>
10: <td>RouteHandler:</td>
11: <td><%=RouteData.RouteHandler != null? RouteData.RouteHandler.GetType().FullName:"" %></td>
12: </tr>
13: <tr>
14: <td>Values:</td>
15: <td>
16: <ul>
17: <%foreach (var variable in RouteData.Values)
18: {%>
19: <li><%=variable.Key%>=<%=variable.Value%></li>
20: <% }%>
21: </ul>
22: </td>
23: </tr>
24: <tr>
25: <td>DataTokens:</td>
26: <td>
27: <ul>
28: <%foreach (var variable in RouteData.DataTokens)
29: {%>
30: <li><%=variable.Key%>=<%=variable.Value%></li>
31: <% }%>
32: </ul>
33: </td>
34: </tr>
35: </table>
36: </div>
37: </form>
38: </body>
在添加的Global.asax文件中,我們將路由注冊操作定義在Application_Start方法中。如下麵的代碼片斷所示,映射到weather.aspx頁麵的URL模板為在調用MapPageRoute方法的時候,我們還為定義在URL模板的兩個變量定義了默認值以及正則表達式。除此之外,我們還在注冊的路由對象上附加了兩個變量,表示對變量默認值的說明(defaultCity:BeiJing;defaultDays:2)。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 }};
6: var constaints = new RouteValueDictionary { { "areacode", @"0\d{2,3}" }, { "days", @"[1-3]{1}" } };
7: var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
8:
9: RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, constaints, dataTokens);
10: }
11: }
由於我們為定義在URL模板中表示區號和天數的變量定義了默認值(areacode:010;days:2),如果我們希望返回北京地區未來兩天的天氣,可以直接訪問應用根地址,也可以隻指定具體區號,或者同時指定區號和天數。如下圖所示,當我們在瀏覽器地址欄中輸入上述三種不同的URL會得到相同的輸出結果。
從下圖所示的路由信息我們可以看到,默認情況下RouteData的Route屬性類型正是Route,而RouteHandler屬性則一個是PageRouteHandler對象,我們會在本章後續部分對PageRouteHandler進行詳細介紹。通過地址解析出來的變量被存儲數Values屬性中,而在進行路由注冊過程為Route對象DataTokens屬性定義的變量被轉移到了RouteData的同名屬性中。[實例源代碼下載]
我們以電話區號代表對應的城市,為了確保用戶在的請求地址中提供有效的區號,我們通過正則表達式()對其進行了約束。此外,我們隻能提供未來3天以內的天氣情況,我們同樣通過正則表達式(“”)是對請求地址中表示天數的變量進行了約束。如果請求地址中的內容不能符合相關變量段的約束條件,則意味著對應的路由對象與之不匹配。
對於本例來說,由於我們隻注冊了唯一的路由對象,如果請求地址不能滿足我們定義的約束條件,則意味著找不到一個具體目標文件,會返回404錯誤。如下圖所示,由於在請求地址中指定了不合法的區號(01)和天數(4),我們直接在瀏覽器界麵上得到一個HTTP 404錯誤。
對於約束,除了可以通過字符串的形式為某個變量定義相應的正則表達式之外,我們還可以指定一個實現了IRouteConstraint接口的類型的對象對整個請求進行約束。如下麵的代碼片斷所示,IRouteConstraint具有唯一的方法Match用於定義約束的邏輯,該方法的5個參數分別表示:HTTP上下文、當前路由對象、約束的名稱(存儲約束對象的RouteValueDictionary的Key)、解析被匹配URL得到的變量集合以及表示路由的方向。
1: public interface IRouteConstraint
2: {
3: bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
4: }
5: public enum RouteDirection
6: {
7: IncomingRequest,
8: UrlGeneration
9: }
所謂路由的方向表示是針對請求匹配(入棧)還是針對URL的生成(出棧),分別通過如上所示的枚舉類型RouteDirection的兩個枚舉值表示。具體來說,當調用路由對象的GetRouteData和GetVirtualPathData方法時,枚舉值IncomingRequest和UrlGeneration分別被采用。
ASP.NET路由係統的應用編程接口中定義了如下一個實現了IRouteConstraint接口的HttpMethodConstraint類型。顧名思義,HttpMethodConstraint提供針對HTTP方法(GET、POST、PUT、DELTE等)的約束。我們可以通過HttpMethodConstraint為路由對象設置一個允許的HTTP方法列表,隻有方法名稱在這個指定的列表中的HTTP請求才允許被路由。這個被允許被路由的HTTP方法列表對於HttpMethodConstraint的隻讀屬性AllowedMethods,並在構造函數中初始化。
1: public class HttpMethodConstraint : IRouteConstraint
2: {
3: public HttpMethodConstraint(params string[] allowedMethods);
4: bool IRouteConstraint.Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
5: public ICollection<string> AllowedMethods { get; }
6: }
同樣是針對我們演示的例子,我們在進行路由注冊的時候通過如下的代表應用了一個類型為HttpMethodConstraint的約束,並將允許的HTTP方法設置為POST,意味著被注冊的Route對象僅限於路由POST請求。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } };
6: var constaints = new RouteValueDictionary { { "areacode", @"0\d{2,3}" }, { "days", @"[1-3]{1}" }, { "httpMethod", new HttpMethodConstraint("POST") } };
7: var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
8:
9: RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, constaints, dataTokens);
10: }
11: }
現在我們采用與注冊的URL模板相匹配的地址(/010/2)來訪問Weather.aspx頁麵,依然會得到如下圖所示的404錯誤。[實例源代碼下載]
在成功注冊路由的情況下,如果我們按照傳統的方式訪問一個物理文件(比如.asxp、.css或者.js等),在請求地址滿足某個路由的URL模板模式的情況下,ASP.NET是否還是正常實施路由呢?我們不妨通過我們的實例還測試一下。為了讓針對某個物理文件的訪問地址也滿足注冊路由對象的URL模板模式,我們需要按照如下的方式將上麵定義的關於正則表達式約束刪除。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 }};
6: //var constaints = new RouteValueDictionary { { "areacode", @"0\d{2,3}" }, { "days", @"[1-3]{1}" } };
7: var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
8:
9: RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, null, dataTokens);
10: }
11: }
當我們通過傳統的方式來訪問存放於根目錄下的weather.aspx頁麵時會得到如下圖所示的結果。從界麵上的輸出結果我們不難看出,雖然請求地址完全滿足我們注冊路由對象的URL模板模式,但是ASP.NET並沒有對請求地址實施路由。原因很簡單,如果中間發生了路由,基於頁麵的RouteData的各項屬性都不可能為空。[實例源代碼下載]
那麼是否意味著如果請求地址對應著一個現存的物理文件,ASP.NET就會自動忽略路由呢?實則不然,或者說不對現有文件實施路由僅僅默認采用的行為。是否對現有文件實施路由取決於代表全局路由表的RouteCollection對象的RouteExistingFiles屬性,該屬性默認情況下為False,我們可以將此屬性設置為True使ASP.NET路由係統忽略現有物理文件的存在,總是按照注冊的路由表進行路由。為了演示這種情況下,我們對Global.asax文件作了如下的改動,在進行路由注冊之前將RouteTable的Routes屬性代表的RouteCollection對象的RouteExistingFiles屬性設置為True。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: RouteTable.Routes.RouteExistingFiles = true;
6: var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 } };
7: var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
8:
9: RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, null, dataTokens);
10: }
11: }
依舊是針對weather.aspx頁麵的訪問,我們卻得到不一樣的結果。從下圖中我們可以看到,針對頁麵的相對地址weather.aspx不再指向具體的Web頁麵,在這裏就是一個表示獲取的天氣信息對應的目標城市(areacode=weather.aspx)。[實例源代碼下載]
如果將代表全局路由表的RouteTable的靜態屬性Routes的RouteExistingFiles屬性設置為True,意味著ASP.NET針對所有抵達的請求都一視同仁,都按照注冊的路由表進行注冊,但這會代碼一些問題。不知道讀者有沒有發現上圖所示的頁麵具有不一樣的格式(標簽部分沒有加粗,也沒有居右上對齊),這是因為這是采用了jQuery的方式來控製的,為此我們必須按照如下的方式來飲用jQuery相關的腳本文件。
1: <script src="/jquery-1.4.1.min.js" type="text/javascript"></script>
但是由於我們將全局路由表的RouteExistingFiles屬性設置為True,意味著針對上麵這個.js腳本文件的訪問也會被路由。根據我們注冊的路由規則,針對這個文件的訪問會自動被導向weather.aspx這個頁麵。如下圖所示,我們直接在瀏覽器的地址欄中屬性.js文件的地址,呈現出來還是我們熟悉的界麵(areacode=jquery-1.4.1.min.js)。[實例源代碼下載]
這是一個不得不解決的問題,因為它是我們無法正常地在頁麵中引用向javascript和css文件。我們可以通過調用RouteCollection的Igore方法來注冊一些需要讓路由係統忽略的URL模板。從前麵給出的關於RouteCollection的定義我們可以看到它具有兩個Igore重載,除了指定需要忽略的URL模板之外,我們還可以對相關的變量定義約束正則表達式。為了讓ASP.NET路由係統忽略掉針對.js文件請求,我們可以按照如下的方式在Global.asax中調用RouteTable的Routes屬性的Ignore方法。[實例源代碼下載]
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: RouteTable.Routes.RouteExistingFiles = true;
6: RouteTable.Routes.Ignore("{filename}.js/{*pathInfo}");
7: //其他操作
8: }
9: }
當我們調用RouteCollection對象的MapPageRoute方法進行路由注冊的本質就在路由字典中添加Route對象,所以我們完全調用Add方法添加一個手工創建的Route對象,如下所示的兩種路由注冊方式是完全等效的。如果我們需要添加一個繼承自RouteBase的自定義路由對象,我們不得不采用手工添加的方式。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 }};
6: var constaints = new RouteValueDictionary { { "areacode", @"0\d{2,3}" }, { "days", @"[1-3]{1}" } };
7: var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
8:
9: //路由注冊方式1
10: RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, constaints, dataTokens);
11:
12: //路由注冊方式2
13: Route route = new Route("{areacode}/{days}", defaults, constaints, dataTokens, new PageRouteHandler("~/weather.aspx", false));
14: RouteTable.Routes.Add("default", route);
15: }
16: }
ASP.NET的路由係統:URL與物理文件的分離
ASP.NET的路由係統:路由映射
ASP.NET的路由係統:根據路由規則生成URL
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 12:04:13