1. 程式人生 > >ASP.NET的路由系統:路由對映

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模板為{areacode}/{days}。在呼叫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的同名屬性中。[例項原始碼下載]

clip_image002

二、約束

我們以電話區號代表對應的城市,為了確保使用者在的請求地址中提供有效的區號,我們通過正則表示式(“0\d{2,3}”)對其進行了約束。此外,我們只能提供未來3天以內的天氣情況,我們同樣通過正則表示式(“[1-3]{1}”)是對請求地址中表示天數的變數進行了約束。如果請求地址中的內容不能符合相關變數段的約束條件,則意味著對應的路由物件與之不匹配。

對於本例來說,由於我們只註冊了唯一的路由物件,如果請求地址不能滿足我們定義的約束條件,則意味著找不到一個具體目標檔案,會返回404錯誤。如下圖所示,由於在請求地址中指定了不合法的區號(01)和天數(4),我們直接在瀏覽器介面上得到一個HTTP 404錯誤。

clip_image004

對於約束,除了可以通過字串的形式為某個變數定義相應的正則表示式之外,我們還可以指定一個實現了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錯誤。[例項原始碼下載]

clip_image006

三、對現有檔案的路由

在成功註冊路由的情況下,如果我們按照傳統的方式訪問一個物理檔案(比如.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的各項屬性都不可能為空。[例項原始碼下載]

clip_image008

那麼是否意味著如果請求地址對應著一個現存的物理檔案,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<