天天看點

在ASP.NET中實作Url Rewriting

1.1. 概要

分析如何使用微軟提供的ASP.NET來對動态産生的URL位址進行網址重寫。網址重寫是實作一種截取網址請求并将其進行處理後重新指向到一個指定的網址的過程。作者本人在對各種實作網址重寫的技術進行研究和探讨後得出的經驗和方法,希望能對您有所幫助。

1.2. 内容簡介

稍微花點時間看一看你做的網站裡頭的URL位址,你看到類似這樣的位址嗎 http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary ?也許你會出于某種目的把大量的頁面檔案從一個目錄甚至一個網站轉移到其他地方,而許多通路者出于個人興趣或者研究目的之前就已經将原有網址收藏了起來, 如果這時他從收藏夾打開該頁面的時候發現這已經是壞鍊了。本文旨在介紹如何使用網址重寫将那些“難看”的網址轉換成比較有實際意義的網址,使其便于記憶。例如将http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099& type=summary轉換成如下位址:http://yoursite.com/ dispEmployeeInfo/459-099/summary.html 。我們甚至發現網址重寫技術可以解決令人頭疼的404錯誤,或者說它可以建立一個智能化的404錯誤解決方案。

如上所述,網址重寫是實作一種截取網址請求并将其進行處理後重新指向到一個指定的網址的過程。在網址重寫執行的期間,相應處理程式處理被請求的網址,從中提取出相關的值,然後重新指向一個新的指定位址。例如:由于一次網站目錄調整,原有的 /people/ 子目錄下的所有網頁全部移動到/info/employees/目錄,原通路者從收藏夾或者其他什麼地方點選連結發出通路/people/目錄下的檔案的請求時,你肯定希望他還是能通過原有位址看到和原來相同的頁面,但實際上看到的卻是網址重寫指向的新目錄下的相應檔案。

在老版本ASP中,使用網址重寫技術的途徑很少,要麼寫一個ISAPI過濾器,要麼購買第三方廠商提供的網址重寫元件,然而在微軟提供的ASP.NET下你可以通過多種方法很簡單地開發出自己的網址重寫軟體,以滿足自己各種不同的需要。本文将和你一起讨論這門針對ASP.NET開發人員的實作網址重寫的技術,然後舉一些網址重寫實際應用的例子。在我們深入探讨網址重寫技術的細節之前,我們先看一下日常使用網址重寫技術實作的場景。

1.3. 網址重寫的一般用途

建立一個資料操作的ASP.NET程式最常見的就是一個 aspx頁面後面帶上一些查詢參數集合。例如在設計一個電子商務網站的時候,假定你設計了一項功能允許使用者浏覽待售的商品,為了更加友善操作,你設計了一個頁面displayCategory.aspx将商品按照給定的分類顯示,那麼該分類下的商品顯示頁面上應該在頁面檔案對應網址後面加上了一個商品分類的查詢參數,例如使用者要查詢待售的“裝飾品”,在資料庫中所有的裝飾品資料對應的分類編号CategoryID的值為5,那麼使用者會通路如下網址:http://yoursite.com/displayCategory.aspx?CategoryID=5。

建立一個包含類似這樣網址的網站最終有兩種結果,首先從最終使用者的角度來觀察,http://yoursite.com /displayCategory.aspx?CategoryID=5 這個網址有些雜亂,可行性分析專家Jakob Neilson(首頁:http://useit.com/) 建議選擇網址顯示方式時候考慮如下要求(參考網址:http://www.useit.com/alertbox/990321.html):

     ● 是否簡短

     ● 是否易于輸入

     ● 是否将站點結構形象化

     ● 是否具有隐蔽性,也就是讓使用者通過一個虛拟的看似有意義的導航位址通路指向該位址

我想還應該在上述清單中再增加一條:是否便于記憶。http://yoursite.com /displayCategory.aspx?CategoryID=5 這個位址沒有一個地方符合Neilson标準的任何一條,也不便于記憶。當然,對于有經驗的網絡開發專家來說,他們很熟悉這種鍵值對構成的查詢參數結構體系,然而對于普通使用者來說輸入這些帶有參數的網址實在是太麻煩了。

一種較好的方法就是使用一種比較直覺且容易記憶的方式來将網址表示為:http://yoursite.com/products/Widgets 乍一看很容易就會推斷這個網址所對應的内容極有可能會是顯示裝飾品(Widgets)資訊,這個網址就變得更加容易記憶和傳播!然後我告訴我的同僚:“請檢視這個網址:http://yoursite.com/products/widgets ”不用我說第二遍,她可能一次就把位址敲到浏覽器上了(你也可以在亞馬遜(Amazon.com)的網站上這樣嘗試一下)。很快就浏覽器上就列出了裝飾品(Widgets)的内容。這裡“隐蔽性”表示:使用者可以自行變更網址的結尾,例如輸入:http://yoursite.com/products 就能看到全部分類相關的商品清單或者列出所有相關商品分類目錄清單。

注:用上述簡單的變更網址内容的方法來構思一下如今的比較流行的Blog網站生成的網址。例如:要查詢2004年1月28日所發的文章,隻需輸入 http://someblog.com/2004/01/28  即可,如果将網址裁減為 http://someblog.com/2004/01 則顯示 2004年1月份的文章 ,同樣将月份裁減掉得到 http://someblog.com/2004 則顯示出2004年全年所發的文章。

網址重寫技術除了用于将複雜的網址簡單化之外,它還能用于處理因網站目錄調整或者其他原因導緻産生大量的無效連結和過期書簽。

1.4. 當一個Web請求傳送到IIS會發生什麼?

在探讨如何實作網址重寫這項技術之前,很有必要了解一下IIS是處理所接收的Web請求的機制。當一個Web請求到達IIS Web伺服器時,IIS會根據所請求的檔案字尾名來決定如何處理該請求,IIS可以處理諸如HTML頁面、圖檔、靜态内容,或者将請求轉發給ISAPI應用程式,由該ISAPI應用程式處理後生成HTML靜态内容傳回給IIS,最後由IIS将請求結果發送回給用戶端。(一個ISAPI應用程式就是一套編譯好能随時在背景運作的類庫,它的任務就是根據請求生成相關的内容。)

例如:如果IIS接收到一個對Info.asp的請求,它會将該請求轉交給 asp.dll來處理,該ISAPI應用程式調出并執行所請求的ASP頁面,然後把生成的HTML代碼傳回給IIS,IIS最後把内容發送回請求用戶端。對于ASP.NET頁面,IIS則将請求轉交給名為aspnet_isapi.dll的ISAPI應用程式來處理,該ISAPI應用程式調用托管的 ASP.NET工作程序來處理該請求,并将生成的HTML代碼傳回給請求用戶端。

你可以自定義IIS,将某一類擴充名映射到指定的ISAPI應用程式,圖一顯示了IIS管理工具中的應用程式配置對話框。

在ASP.NET中實作Url Rewriting

圖一.已配置的檔案擴充名映射

關于對IIS如何管理所接收的請求的詳細探讨有些超出本文内容,想了解的更詳細的話可以參考Michele Leroux Bustamante的著作Inside IIS and ASP.NET(深入研究IIS與ASP.NET),重要的是要了解ASP.NET引擎隻負責處理對擴充名已經被正确配置映射到aspnet_isapi.dll的網絡請求。

1.5. 用ISAPI過濾器來分析請求

除了将請求的檔案擴充名映射到相應的ISAPI應用程式外,IIS還執行一些其他工作。例如IIS還主動對送出請求的用戶端使用者進行授權,并判斷已授權使用者是否對其請求的檔案擁有通路權限,在一個請求過程的全部生命期内,IIS的處理經曆了幾個階段,在每一個階段IIS都生成一個事件,而該事件可以被ISAPI過濾器實時操控的。

如同ISAPI應用程式一樣,ISAPI過濾器也是一塊塊安裝在Web伺服器上的非托管代碼。ISAPI應用程式用于對所接收的特定檔案類型做出響應,而ISAPI過濾器含有對IIS生成的事件做出響應的代碼(contain Code),甚至可以編輯進出的資料。ISAPI也含有衆多應用程式,包括:

    · 權限控制與授權(Authentication and Authorization)

    · 日志記錄與監視(Logging and Monitoring)

    · HTTP内容壓縮(HTTP Compression)

    · 網址重寫(URL Rewriting)

本文所探讨的用ASP.NET實作的網址重寫技術就是基于ISAPI過濾器用于網址重寫的技術内容,然而我們仍然要讨論一下究竟是使用ISAPI過濾器還是使用ASP.NET應用程式提供的技術來實作網址重寫技術。

1.6. 當一個請求傳入ASP.NET引擎的時候會發生什麼?

ASP.NET問世之前,在IIS Web伺服器上的網址重寫功能需要通過ISAPI過濾器來實作,自從這個家夥問世後我們就能通過ASP.NET來實作URL重寫了,因為ASP.NET的解釋引擎與IIS有極大的相似之處,産生這些相似性主要是因為ASP.NET:

    · 在處理接收的請求的生命期内也會産生事件;

    · 允許任意數量的HttpModule操控産生的事件,這與IIS中的ISAPI過濾器類似;

    · 将請求的資源委托給HttpHandler處理,這與IIS中的ISAPI應用程式類似。

和IIS一樣,在一個請求的整個生命期内,ASP.NET對該請求的處理狀态發出的狀态改變信号引發相應的事件。例如:BeginRequest事件在ASP.NET開始響應用戶端請求之始引發;AuthenticateRequest事件在ASP.NET确立使用者身份後引發,當然還有諸如AuthorizeRequest,ResolveRequestCache和EndRequest等其它很多事件,這些都是System.Web.HttpApplication類下的事件,更多資訊請參考技術文檔中的類HttpApplication概要(HttpApplication Class Overview)。

如上所述,可以建立ISAPI過濾器并用于相應IIS引發的事件,同理,ASP.NET也提供了HttpModule用于響應ASP.NET引擎引發的事件,一個ASP.NET應用程式通過配置可以擁有多個HttpModule。ASP.NET引擎每處理一個請求,便初始化一個相應配置好的HttpModule,并允許它針對請求處理期間引發的事件生成相應的事件委托。事實上ASP.NET引擎處理每一個請求調用大量的事件委托。FormsAuthenticationModule就是衆多内嵌HttpModule中的一個,它首先檢查是否使用表單授權,如果是的話,它将檢查使用者是否已授權,如果沒有授權則自動把使用者重定向到指定的登入頁面。

回憶在IIS中,一項請求最後被轉交給一個ISAPI應用程式處理,該應用程式針對每一項請求進行處理并傳回相應的資料。例如,用戶端發出一個通路經典 ASP頁面的請求,IIS将該請求轉交給asp.dll程式處理,asp.dll針對該請求執行asp頁面内容,并傳回HTML編碼。ASP.NET也使用了類似的手法,ASP.NET引擎在将這些HttpModule初始化後,判斷并決定調用相應的HttpModule來處理該請求。

所有通過ASP.NET引擎解析的請求最終被送交一個HttpHandler或者HttpHandlerFactory(一個HttpHandler隻是簡單地傳回一個用于處理該請求的HttpHandler的執行個體。)最終的委托呈現并響應所請求的HTML編碼,并發送回IIS,IIS則将HTML傳回給請求用戶端。

ASP.NET包含許多HttpHandler,例如,PageHandlerFactory是用于呈現ASP.NET頁面内容,WebServiceHandlerFactory用于呈現ASP.NET Web服務的SOAP資料包,TraceHandler用于将ASP.NET請求資源的HTML标記寫入trace.axd。

圖二描繪了一個針對ASP.NET資源的請求所經過的處理流程。首先,IIS接收到該請求并将其轉交給aspnet_isapi.dll。其次,ASP.NET引擎将一些HttpModule初始化。最後,最終的HttpHandler被調用,生成相應的标記語言,并将其傳回給IIS,最終傳回到請求用戶端。

在ASP.NET中實作Url Rewriting

圖二.IIS和ASP.NET對請求的處理過程

1.7. 建立并注冊自定義HttpModule和HttpHandler

建立自定義HttpModule的工作相對較簡單,它包括一個實作目前接口的托管類,HttpModule必須實作System.Web.IHttpModule接口,同樣HttpHandler和HttpHandlerFactory必須分别實作System.Web.IHttpHandler接口和System.Web.IhttpHandlerFactory接口。有關建立HttpHandler和HttpModule的細節已經超出本書範圍,要了解更多詳情請參閱Mansoor Ahmed Siddiqui的文章《HttpHandlers and HTTP modules in ASP.NET》(ASP.NET中的HttpHandler和HttpModule)。

一旦HttpModule和HttpHandler被建立後,必須向Web應用程式注冊。如果要向整個Web伺服器HttpModule和HttpHandler隻需簡單的寫入machine.config檔案;如果是由指定的Web應用程式調用則需在該程式的web.config配置檔案中添加幾行XML标記。

例如,要向指定的Web應用程式注冊HttpModule和HttpHandler,隻需向該Web應程式的web.config配置檔案中configuration\System.Web節中添加下列幾行:

在ASP.NET中實作Url Rewriting

< HttpModules >

在ASP.NET中實作Url Rewriting

     < add  type ="type"  name ="name"   />

在ASP.NET中實作Url Rewriting

</ HttpModules >

其中type屬性為HttpModule的辨別号和類庫名稱,name屬性則為該子產品取一個較為友好的名稱友善在Global.asax調用。

HttpHandler和HttpHandlerFactory則是在web.config檔案中configuration\System.Web節中添加<httpHandler>标記,例如:

在ASP.NET中實作Url Rewriting

< httpHandlers >

在ASP.NET中實作Url Rewriting

     < add  verb ="verb"  path ="path"  type ="type"   />

在ASP.NET中實作Url Rewriting

</ HttpModules >

回憶上文,ASP.NET對每一個接收到的請求指派相應的HttpHandler來處理并呈現相應内容,該指派決定于所接收請求的verb和path的内容,verb為HTTP請求的類型:GET或者POST,path則為請求的檔案的路徑和檔案名。如果我們打算用一個HttpHandler來處理所有GET類型和POST類型的并且檔案擴充名為.scott的内容,可以在web.config相應配置節中加入下列标記:

在ASP.NET中實作Url Rewriting

< httpHandlers >

在ASP.NET中實作Url Rewriting

     < add  varb ="*"  path =".scott"  type ="type"   />

在ASP.NET中實作Url Rewriting

</ httpHandlers >

其中type是我們定義的HttpHandler的類型。

注意:在注冊HttpHandler的時候必須注意HttpHandler所使用的檔案擴充名必須已經在IIS中做指向ASP.NET引擎的映射,在上面.scott擴充名的例子中,如果我們所使用的.scott擴充名如果沒有在 IIS中做指向ASP.NET引擎的映射的話,假定對foo.scott檔案送出請求,該請求将導緻IIS将foo.scott檔案内容直接呈現給用戶端,為了能夠讓HttpHandler處理該請求,必須将.scott擴充名在IIS中做指向ASP.NET引擎的映射,之後IIS才能正确地将.scott的請求轉交給相應的HttpHandler。

有關HttpModule和HttpHandler更詳細的内容請參閱MSDN中<HttpModules>節和<httpHandlers>節的文檔資訊。

<HttpModules>文檔參考;

<httpHandlers>文檔參考。

1.8. 實作網址重寫

網址重寫技術不但可以在IIS Web伺服器一級通過ISAPI過濾器實作,而且還可以在ASP.NET一級通過HttpModule或者HttpHandler實作。本文主要關注在ASP.NET一級實作網址重寫技術,是以此時不必關注在ISAPI應用程式中實作網址重寫的技術細節,而且有很多第三方廠商提供的ISAPI過濾器,比如

    Helicon的ISAPI Rewrite;

    QwerkSoft的IIS Rewrite;Port80的PageXChanger;

    等等。

1.9. 建構網址重寫引擎

在ASP.NET中實作網址重寫很簡單,隻需調用System.Web.HttpContext類的RewritePath()方法即可。HttpContext類中包含有關于特定HTTP請求的HTTP規範資訊。ASP.NET引擎每接收到一個特定請求後便針對該請求建立一個特定的執行個體,這個類包含一些屬性諸如:Request和Response屬性,分别提供對請求和響應的通路;Application和Session屬性提供對Application變量和Session變量的通路;User屬性提供對已授權使用者資訊的通路。

在微軟.NET Framework 1.0版本中,RewritePath()方法接收一個新路徑的簡單字元串,在其内部HttpContext類的RewritePath(string)方法内在地更新Request對象的路徑和查詢參數。除了RewritePath(string)方法之外,.NET Framework 1.1版還提供了另外一些重載版本,其中一個重載版本接收三個輸入字元串參數,這種交替的重載形式不僅僅隻是設定Request對象的路徑和查詢參數這些屬性,而是設定更深層的成員變量,這些成員變量用于為PhysicalPath、PathInfo、FilePath屬性計算Request對象值。

為了實作ASP.NET中的網址重寫,我們需要建立一個HttpHandler和HttpModule用于:

 根據請求的路徑決定所需要重寫的路徑;

 重寫路徑,如果需要的話可以調用RewritePath方法;

以前文所建構的那個站點為例,可以通過/info/employee.aspx?empID=EmployeeID來通路每一個雇員的資訊。為了使這個網址更加地具有“隐蔽性”,我們可能會使用更加容易了解的通路方式如:/people/雇員名.aspx。這裡就有了一個網址重寫的案例:當接收到對 /people/ScottMitchell.aspx的請求的時候,我們就得使用網址重寫使得對該頁面的請求被重寫指向到先前使用的/info /employee?EmpID=1001位址。

1.10. 使用HttpModule來調用網址重寫

在ASP.NET一級來執行網址重寫,既可以使用HttpHandler,也可以使用HttpModule。當使用HttpModule的時候,必須決定如果該網址需要被重寫的話,究竟應該在整個請求的生命周期期間的那一個點來使用。乍一看着有些武斷,但是這個決定以重大而且微妙的方式影響到你的應用程式。之是以作出對網址重寫點的選擇是因為内嵌的ASP.NET HttpModule使用Request對象的屬性值來完成自己的工作(回憶一下重寫路徑對Request對象的屬性值的改變),這些内嵌HttpModule和相應事件的密切關系列舉如下:

HttpModule 事件 簡介
FormsAuthenticationModule AuthenticateRequest 判斷使用者是否已認證表單授權方式擷取授權,如果沒有的話則将使用者重定向到指定的登入頁面
FileAuthorizationModule AuthorizeRequest 當使用Windows授權方式的時候,該HttpModule判斷并确定該Microsoft Windows帳戶是否對其請求的資源擁有足夠的權限
UrlAuthorizationModule AuthorizeRequest 檢查并确認請求者是否對所通路的網址擁有權限。該Url授權可以在web.config檔案的<authorization>和<location>元素中配置

回想一下 BeginRequest事件在 AuthenticateRequest事件之前引發,而 AuthenticateRequest事件又在 AuthorizeRequest事件之前引發。

實作網址重寫的一個較為安全的場合就是把它放在在 BeginRequest事件中執行,這意味着如果要執行網址重寫的話,在衆多内嵌 HttpModule運作的時候他已經完成了。這種途徑的最終用途淋漓盡緻地展現在表單驗證上。當使用者通路受限資源的時候,如果之前使用了表單驗證,他會自動被重定向到指定的登入頁面,在成功登入之後,使用者被重定向回先前試圖通路的受限制頁面。

如果把網址重寫放在 BeginRequest事件或者 AuthenticateRequest事件中,在登入頁面上執行送出後,該頁面會将使用者重定向到網址重寫指定的頁面。假定當使用者在浏覽器上敲入/people /ScottMitchell.aspx位址,該位址是要被重定向到/info/employee.aspx?EmpID=1001的,如果該Web應用程式設定使用表單驗證,當使用者開始通路/people/ScottMitchell.aspx的時候,該網址将重寫指向/info /employee.aspx?EmpID=1001,接着 ForumAuthenticationModule啟動,如果需要的話将使用者重定向到登入頁面,使用者登入後重定向到的頁面将是/info/employee.aspx?EmpID=1001,這也是自從 FormAuthenticationModule啟動運作時所送出請求的頁面。

同上類似,當把網址重寫放在BeginRequest事件或者AuthenticateRequest事件中運作的時候, UrlAuthenticationModule也發現了網址重寫指向的網址,這意味着如果在該應用程式的web.config檔案中<location>節為特定的網址配置特定的授權位址的話,你得引用重寫所指向的網址。

為了解決這個微妙的問題,一個可能就是把網址重寫放在 AuthorizeRequest事件中運作,但是在使用這種方法解決URL授權和表單授權的異常時又引入了一個新的缺陷:檔案授權會失效。當使用Windows驗證的時候, FileAuthorizationModule檢查并驗證已認證驗證的使用者是否擁有足夠的權限通路特定的ASP.NET頁面。

假定有一群使用者并沒有Windows級别的通路權限通路C:\inetpub\wwwroot\info\employee.aspx,當這些使用者試圖通路 /info/employee.aspx?EmpID=1001的時候,他們會得到未授權的錯誤,如果我們把網址重寫放到 AuthenticateRequest事件中運作,當 FileAuthorizationModule驗證該安全性設定的時候,他仍任人為被請求的檔案是/people/ScottMitchell.aspx,而這時該網址已經被重寫了,是以 FileAuthorizationModule會直接放行,讓使用者看到了網址重寫指向的内容:/info/employee.aspx?Empid=1001。

那麼什麼時候在 HttpModule調用網址重寫合适呢?他決定于所使用的驗證方式,當然如果不使用驗證方式的話,那麼無論是在 BeginRequest事件、 AuthenticateRequest事件還是 AuthorizeRequest事件中調用網址重寫沒有多大差別,如果使用表單驗證方式并且不使用Windows驗證方式的話,把網址重寫放入 AuthorizeRequest事件委托中調用既可,如果使用Windows驗證方式的話,把這項功能放入 BeginRequest事件或者 AuthenticateRequest事件調用就行了。

1.11. 使用HttpHandler來調用網址重寫

除了上面所述方法外,網址重寫也可以放入HttpHandler或者HttpHandlerFactory中調用。HttpHandler是一個負責針對特定請求生成相應内容的類,而HttpHandlerFactory傳回一個HTTP的執行個體,該執行個體針對特定請求生成相應内容。

本節将着眼于為這些ASP.NET頁面建立一個網址重寫的HttpHandlerFactory。建立HttpHandlerFactory必須實作IHTTPHandlerFactory接口,它包括一個GetHandler()方法。ASP.NET引擎在初始化這些HttpModule後做出決定針對該請求調用相應的HttpHandler或者HttpHandlerFactory,在調用HttpHandlerFactory的時候,針對該Web請求以及随同的其他資訊的HttpContext中經過的的HttpHandlerFactory的GetHandler()方法将被ASP.NET引擎調用,HttpHandlerFactory必須傳回一個能委托該請求的對象,并且該對象要能實作IHttpHandler接口。

要通過一個HttpHandler來調用網址重寫,可以先建立一個HttpHandlerFactory,它的GetHandler()方法檢查所請求的網址并決定是否需要調用網址重寫。如果要調用網址重寫的話則調用前文所述的已認證檢查的HttpContext對象的RewritePath()方法。最後該HttpHandlerFactory傳回一個由類System.Web.UI.PageParser的GetCompiledInstance()方法傳回的HttpHandler。(這與内嵌于ASP.NET頁面的HttpHandlerFactory(PageHandlerFactory)的工作原理相同。)

在所有HttpModule被初始化後,HttpHandlerFactory就開始被執行個體化。把網址重寫放在這些事件場所的最後一個裡頭調用的時候,也會碰到相同的問題:檔案授權将會失效。如果非要依賴于Windows驗證和檔案驗證的時候,你可能得使用HttpModule來調用網址重寫了。

下一章我們着眼于如何建構一個可重用的網址重寫引擎,使用下文所提的這些示例均以真實案例作為參照,在作者首頁上提供下載下傳。先用用一個簡單的網址重寫的例子來探讨如何實作網址重寫,緊接着将利用網址重寫引擎中正規表達式的強大處理能力來展示真正“隐蔽”的網址重寫技術!

1.12. 使用網址重寫引擎實作簡單的網址重寫

為了便于在Web應用程式中實作網址重寫,我建構了一個網址重寫引擎,該引擎提供下列功能:

 可以在web.config檔案中為頁面開發者定義其所使用的網址重寫引擎的規則;

 通過使用正規表達式來使所制定的網址重寫規則具有更加強大的重寫能力;

 能夠通過簡單配置即可在HttpModule和HttpHandler中使用網址重寫。

本節隻探讨通過HttpModule來實作網址重寫,要了解如何通過HttpHandler來實作網址重寫請下載下傳本文提供的代碼。

1.12.1. 設定網址重寫引擎的配置資訊

我們來探讨一下在web.config中網址重寫規則的配置節。首先必須在web.config檔案中指出是否需要在HttpHandler或者HttpModule中調用網址重寫,在web.config中,下文已經包含了兩個已經被注釋掉的配置節:

在ASP.NET中實作Url Rewriting

<!--

在ASP.NET中實作Url Rewriting

<HttpModules>

在ASP.NET中實作Url Rewriting

    <add type="URLRewriter.ModuleRewriter,URLRewriter" name="ModuleRewriter"/>

在ASP.NET中實作Url Rewriting

</HttpModules>

在ASP.NET中實作Url Rewriting

-->

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

<!--

在ASP.NET中實作Url Rewriting

<httpHandlers>

在ASP.NET中實作Url Rewriting

 <add verb="*" path="*.aspx" type="URLRewriter.RewriterFactoryHandler,URLRewriter" />

在ASP.NET中實作Url Rewriting

</httpHandlers>

在ASP.NET中實作Url Rewriting

-->

在ASP.NET中實作Url Rewriting

被注釋掉的<HttpModules>為配置使用HttpModule調用網址重寫;注釋掉的<httpHandler>為配置使用HttpHandler調用網址重寫。

不論配置使用<HttpModules>還是<httpHandlers>調用網址重寫,除此之外還須配置網址重寫規則,一條重寫規則包括兩項字元串:請求URL中的查找模式和針對該模式的比對成功後的替換字元串。該資訊在web.config檔案中用下列标簽描述:

在ASP.NET中實作Url Rewriting

< RewriterConfig >

在ASP.NET中實作Url Rewriting

     < Rules >

在ASP.NET中實作Url Rewriting

         < RewriterRule >

在ASP.NET中實作Url Rewriting

             < LookFor > pattern to look for </ LookFor >

在ASP.NET中實作Url Rewriting

             < SendTo > String to replace pattern with  </ SendTo >

在ASP.NET中實作Url Rewriting

         </ RewriterRule >

在ASP.NET中實作Url Rewriting

         < RewriterRule >

在ASP.NET中實作Url Rewriting

             < LookFor > pattern to look for </ LookFor >

在ASP.NET中實作Url Rewriting

             < SendTo > String to replace pattern with  </ SendTo >

在ASP.NET中實作Url Rewriting

         </ RewriterRule >

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

     </ Rules >

在ASP.NET中實作Url Rewriting

</ RewriterConfig >

在ASP.NET中實作Url Rewriting

每一條規則都用一個<RewriterRule>元素表示,以<LookFor>節表示查詢模式,當查詢模式發現比對字元串時便用<SendTo>節表示的字元串進行替換。這些規則從上到下進行查詢比對,如果找到一個比對則按此規則執行網址重寫,并且停止查找。

配置<LookFor>節要使用正規表達式來進行字元串比對和替換。(在此我們舉一個例子來說明如何使用正規表達式來對字元串進行比對和替換。)既然該查找模式是一個正規表達式,那麼要注意避開對正規表達式保留字元串的直接使用。(正規表達式的保留字元串包括有:.,?,^,$,等等,可以通過在前面加上一個反斜線來引用這些保留字元,例如\.表示引用一個句點)

1.12.2. 使用HttpModule來執行網址重寫

建立一個HttpModule很簡單,隻要建立一個實作IHttpModule接口的類,該IHttpModule接口定義了兩個方法:

 Init(HttpApplication),該方法在HttpModule初始化時引發,通過該方法為HttpApplication事件調用相應的事件委托;

 Dispose(),當相應請求處理結束并發送回IIS調用此方法,通過此方法執行最終所有的清理和回收程式。

為了更加友善地為網址重寫建立HttpModule,從一開始我就建立一個抽象的基類(BaseModuleRewriter),該類實作了IHttpModule接口。在Init(HttpApplication)事件中,它通過BaseModuleRewriter_AuthorizeRequest方法引發了HttpApplication的AuthorizeRequest事件,該BaseModuleRewriter_AuthorizeRequest方法通過該類的Rewrite()方法重寫傳入參數HttpApplication對象的内部請求虛拟路徑(Path)。在BaseModuleRewriter對象中,該Rewrite()方法是抽象的,并且沒有實際内容,但在繼承自該類的對象中必須重載Rewrite()方法并為該方法提供實際内容。

通過對該基類的繼承,所有需要做的工作就是建立一個繼承自BaseModuleRewriter的類,重載Rewrite()方法并在該方法中添加網址重寫邏輯代碼。下文列出BaseModuleRewriter代碼:

在ASP.NET中實作Url Rewriting

public   abstract   class  BaseModuleRewriter : IHttpModule

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

    public virtual void Init(HttpApplication app)

在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

        // WARNING!  This does not work with Windows authentication!

在ASP.NET中實作Url Rewriting

        // If you are using Windows authentication, 

在ASP.NET中實作Url Rewriting

        // change to app.BeginRequest

在ASP.NET中實作Url Rewriting

        app.AuthorizeRequest += new EventHandler(this.BaseModuleRewriter_AuthorizeRequest);

在ASP.NET中實作Url Rewriting

    }

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

    public virtual void Dispose() 

在ASP.NET中實作Url Rewriting

{}

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

    protected virtual void BaseModuleRewriter_AuthorizeRequest(object sender, EventArgs e)

在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

        HttpApplication app = (HttpApplication) sender;

在ASP.NET中實作Url Rewriting

        Rewrite(app.Request.Path, app);

在ASP.NET中實作Url Rewriting

    }

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

    protected abstract void Rewrite(string requestedPath, HttpApplication app);

在ASP.NET中實作Url Rewriting

}

注意:該BaseModuleRewriter類将網址重寫放在AuthorizeRequest事件中調用,如果要使用Windows驗證并使用檔案驗證模式時請修改代碼将網址授權放在BeginRequest或者AuthenticateRequest事件中。

ModuleRewriter繼承自BaseModuleRewriter,并真正意義地實作了網址重寫的操作,該類僅包含一個重載了的方法Rewrite(),其内容如下文所示:

在ASP.NET中實作Url Rewriting

protected   override   void  Rewrite( string  requestedPath, System.Web.HttpApplication app)

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

    // get the configuration rules

在ASP.NET中實作Url Rewriting

    RewriterRuleCollection rules = RewriterConfiguration.GetConfig().Rules;

在ASP.NET中實作Url Rewriting

    // iterate through each rule

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

    for(int i = 0; i < rules.Count; i++)

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

        // get the pattern to look for, and 

在ASP.NET中實作Url Rewriting

        // Resolve the Url (convert ~ into the appropriate directory)

在ASP.NET中實作Url Rewriting

        string lookFor = "^" + 

在ASP.NET中實作Url Rewriting

        RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, rules[i].LookFor) + "$";

在ASP.NET中實作Url Rewriting

        // Create a regex (note that IgnoreCase is set

在ASP.NET中實作Url Rewriting

)

在ASP.NET中實作Url Rewriting

        Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);

在ASP.NET中實作Url Rewriting

        // See if a match is found

在ASP.NET中實作Url Rewriting

        if (re.IsMatch(requestedPath))

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

            // match found - do any replacement needed

在ASP.NET中實作Url Rewriting

            string sendToUrl = RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, re.Replace(requestedPath, rules[i].SendTo));

在ASP.NET中實作Url Rewriting

            // Rewrite the URL

在ASP.NET中實作Url Rewriting

            RewriterUtils.RewriteUrl(app.Context, sendToUrl);

在ASP.NET中實作Url Rewriting

            break;      // exit the for loop

在ASP.NET中實作Url Rewriting

        }

在ASP.NET中實作Url Rewriting

    }

在ASP.NET中實作Url Rewriting

}

在ASP.NET中實作Url Rewriting

該Rewriter()方法以擷取web.config檔案中的網址重寫規則的設定為起始,它通過循環通路各條網址重寫規則,每次均擷取目前規則中的LookFor屬性,用正規表達式驗證并判斷是否查找是否對目前請求的網址是否有比對。

如果發現一條比對,将用目前規則的SendTo值對請求的路徑執行一個正規表達式替換,替換後的位址通過參數的形式傳給RewriterUtils.RewriteUrl()方法,RewriterUtils是一個幫助類,它提供一對HttpModule和HttpHandler都可以使用的靜态方法,RewriterUrl()方法隻是簡單地調用了HttpContext對象的RewritePath()方法。

注意:你已經注意到了當執行正規表達式比對和替換的時候調用了一個RewriterUtils.ResolveUrl()方法。該幫助方法簡單地替換了應用程式路徑中“~”的所有執行個體。

本文附錄中提供所有涉及該網址重寫引擎的代碼下載下傳,我們已經探讨了主要的部分,但是還有其它一些元件諸如将web.config檔案中XML格式化了的網址重寫規則反序列化至一個對象的類定義、通過HttpHandlerFactory實作網址重寫的類定義等。本文最後三節将通過一些真實案例來探讨網址重寫的技術。

1.12.3. 用網址重寫引擎實作簡單的網址重寫

為了更好地示範網址重寫引擎的運作,我們來建立一個ASP.NET Web應用程式來實作簡單的網址重寫引擎。假定我們為一家線上銷售各類商品的公司服務,這些産品劃分為以下類别:

分類編号(CategoryID) 分類名稱(CategoryName)

1 飲料(Beverages)

2 調味品(Condiments)

3 工藝品(Confections)

4 日記本(Diary Products)

... ...

假定已經建立好一個名為ListProductsByCategoryID.aspx的ASP.NET頁面檔案,它通過查詢參數擷取一個分類編号,并根據此編号擷取所有該分類下的所有商品。如果使用者想浏覽所銷售的飲料類商品可以通過 ListProductsByCategoryID.aspx?CategoryID=1來通路,如果使用者想浏覽所銷售的日記本類商品可以通過 ListProductsByCategoryID.aspx?CategoryID=4來通路。假定還有一個頁面 ListCategories.aspx,它列出所有代售商品的分類編号。

顯然這裡發現了一個網址重寫的案例。對于使用者來說他們所輸入的位址不具有任何實際意義并且不具備任何“隐蔽性”,倒不如使用網址重寫引擎讓使用者去通路/Products/Baverage.aspx位址,系統将該位址重寫到 ListProductsByCategoryID.aspx?CategoryID=1。我們可以在web.config檔案中來完成網址重寫任務:

在ASP.NET中實作Url Rewriting

< RewriterConfig >

在ASP.NET中實作Url Rewriting

     < Rules >

在ASP.NET中實作Url Rewriting

         <! —- Rules for products lister -- >

在ASP.NET中實作Url Rewriting

         < RewriterRule >

在ASP.NET中實作Url Rewriting

             < LookFor > ~/Products/Baverage.aspx </ LookFor >

在ASP.NET中實作Url Rewriting

             < SendTo > ~/ListProductsByCategoryID.aspx?CategoryID=1 </ SendTo >

在ASP.NET中實作Url Rewriting

         </ RewriterRule >

在ASP.NET中實作Url Rewriting

     </ Rules >

在ASP.NET中實作Url Rewriting

</ RewriterConfig >

很明顯地看到,搜尋使用者通路的路徑是否比對/Products/Baverage.aspx,如果比對的話,則将網址重寫到/ListProductsByCategoryID.aspx?CategoryID=1。

注意:你會發現<LookFor>節點中避免直接在“Baverage.aspx”中使用句點“.”是因為<LookFor>節點的值是正規表達式的比對模式,在正規表達式中句點符号是一個特殊字元,它表示比對任何一個字元,也就是說如果通路BaverageQaspx時也會發生比對,為了避免發生這個句點引起的比對我們得在該句點符号前面加上一個“\”,表示引用句點符号

通過該規則定義,當使用者通路/Products /Baverage.aspx檔案的時候,他們将看到代售的飲料類商品清單資訊。圖3為通路/Products/Baverage.aspx位址時的浏覽器截圖,注意在浏覽器中位址欄上顯示的是使用者輸入的/Products/Baverage.aspx位址,但是實際通路的位址卻是網址重寫後的 /ListProductsByCategoryID.aspx?CategoryID=1。(事實上,在伺服器上根本就不存在/Products /Baverage.aspx檔案!)

在ASP.NET中實作Url Rewriting

圖三.網址重寫後的對商品分類的請求

和 /Products/Baverage.aspx類似,下一步我們添加其它分類的重寫規則,隻需簡單地在web.config檔案中<Rules>中在添加其他<RewriteRule>節即可。該示範完整的重寫規則集合請參考下載下傳文檔的 web.config檔案中的定義。

為了讓該網址更具有“隐蔽性”,如果讓使用者把/Products/Baverage.aspx後面 Baverage.aspx一段截去,在浏覽器中輸入/Products/來浏覽産品分類清單會更好一些。乍一看,這項任務微不足道,隻需添加一條網址重寫規則将/Products/映射到/ListCategories.aspx即可。然而這裡有一個微妙之處,你必須先建立一個/Products/目錄,并在裡面放一個空檔案Default.aspx。

要認識為什麼這些額外的步驟是必須的,先回顧一下前文。網址重寫引擎是處于ASP.NET一級的,也就是說,如果ASP.NET沒有獲得處理請求的機會的話,網址重寫引擎就不能對輸入的網址請求作出判斷。此外,IIS僅在請求檔案包含相應擴充名時才将請求轉交給ASP.NET引擎。如果使用者通路/Products/,IIS并不知道其擴充名是什麼,于是它檢查該目錄下的檔案看是否包含有預設首頁檔案名(Default.aspx,Default.htm,Default.asp,等等,這些檔案名在IIS管理工具對話框中Web伺服器屬性對話框中的文檔标簽中定義。)當然,如果/Products/目錄不存在的話,IIS将傳回一個HTTP 404錯誤。

是以我們需要建立一個/Products/目錄并在該目錄下額外建立一個空檔案Default.aspx,IIS會檢查該目錄下的檔案,發現有一個預設檔案名Default.aspx,于是将請求轉交給ASP.NET,這樣,網址重寫引擎才能生效。

在ASP.NET中實作Url Rewriting

< RewriterRule >

在ASP.NET中實作Url Rewriting

     < LookFor > ~/Products/Default.aspx </ LookFor >

在ASP.NET中實作Url Rewriting

     < SendTo > ~ListCategories.aspx </ SendTo >

在ASP.NET中實作Url Rewriting

</ RewriterRule >

通過該規則,使用者通路/Products/Default.aspx或者通路/Products/都可以看到如圖四所示的産品分類清單。

在ASP.NET中實作Url Rewriting

圖四.在網址上添加“隐蔽性”

1.12.4.處理回送資料

如果要重寫的網址上包含有伺服器端Web Form并執行資料回送,當該Web Form回送資料時會暴露出真實的網址,也就是說,當使用者通路/Products/Baverage.aspx時,浏覽器上位址欄顯示的也是 /Products/Baverage.aspx,但是實際上是通路 /ListProdutsByCategoryID.aspx?CategoryID=1的内容,如果 ListProductsByCategoryID.aspx頁面執行了資料回送的話,使用者被資料回送定向給原始的 /ListProductByCategoryID.aspx?CategoryID=1頁面上,而不是/Products/Baverage.aspx 頁面。這雖然不是什麼大問題,但是使用者會覺察到點選一個按鈕時網址發生了的變化,這也許會令人不安,因為如果出于網址安全的角度來說,直接把真實的網址暴露出來了。

之是以發生這種現象的原因是當Web Form在呈現之時就明确地設定其action屬性為目前Request對象中檔案路徑的值。當然,在Web Form呈現之時,從/Produts/Baverage.aspx到/ListProductsByCategoryID.aspx?CategoryID=1的網址重寫就已經執行完畢了,這意味着Request對象所彙報的是目前使用者所通路的位址是/ListProductsByCategoryID.aspx?CategoryID=1。這麼看來,隻需讓該伺服器端表單在呈現之時不呈現action屬性即可解決問題了。(對浏覽器來說,如果不設定action屬性的話,那麼在送出的時候将使用其預設值。)

然而不幸的是該Web Form不會允許你指定action屬性,也不會允許你通過設定一些屬性來達到禁用呈現action屬性的目的。得自行繼承System.Web.HtmlControls.HtmlForm這個類,并重載該類的RenderAttribute()方法,明确指出該類不呈現acton屬性。

感謝繼承這個強大的功能,使得我們很簡單就擷取了HtmlForm這個類下所有的功能定義,隻需少量幾行代碼就達到所需目的,完整代碼如下所示:

在ASP.NET中實作Url Rewriting

namespace  ActionlessForm

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

        public class Form:System.Web.UI.HtmlControls.HtmlForm

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

                protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

                        writer.WriteAttribute("name",this.Name);

在ASP.NET中實作Url Rewriting

                        base.Attributes.Remove("name");

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

                        writer.WriteAttribute("method",this.Method);

在ASP.NET中實作Url Rewriting

                        base.Attributes.Remove("method");

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

                        this.Attributes.Render(writer);

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

                        base.Attributes.Remove("action");

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

                        if (base.ID!=null)

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

{

在ASP.NET中實作Url Rewriting

                                writer.WriteAttribute("id",this.ClientID);

在ASP.NET中實作Url Rewriting

                        }

在ASP.NET中實作Url Rewriting

                }

在ASP.NET中實作Url Rewriting

        }

在ASP.NET中實作Url Rewriting

}

在ASP.NET中實作Url Rewriting

對RenderAttributes()方法重載的代碼包含了原類HtmlForm的RenderAttributes()方法全部的代碼内容,隻是簡單地去掉了設定action屬性這一節。(我參考了Lutz Roeder的Reflecter一文中類HtmlForm的源代碼)

當建立并編譯了這個類後,将其添加到引用目錄即可在該ASP.NET Web應用程式中使用。為了将原有HtmlForm類替換,隻需簡單地在頁面頂部添加下列代碼:

在ASP.NET中實作Url Rewriting
在ASP.NET中實作Url Rewriting

<%

在ASP.NET中實作Url Rewriting

@ Register TagPrefix="skm" Namespace="ActionlessForm" Assembly="ActionlessForm" %>

然後将<Form runat=”server”>标簽替換為

在ASP.NET中實作Url Rewriting

< skm:Form  id ="Form1"  method ="post"  runat ="Server" >

并将結束标記</Form>替換為

在ASP.NET中實作Url Rewriting

< skm:Form >

你可以檢視該文檔相關下載下傳中的ListProductsByCategoryID.aspx檔案中的自定義Web Form,該下載下傳已經提供了完整的Visual Studio.NET項目檔案包。

注意:如果你打算進行網址重寫的位址不執行資料回送,則沒有必要使用該自定義Web Form的類。

1.13. 建立真正“隐蔽”的網址

上一節簡單網址重寫的示例展示了如何通過新的網址重寫規則來輕松地配置網址重寫引擎,本節将通過出衆的正規表達式來展示網址重寫的強大威力。

時下正在流行Blog,很多人都擁有一個自己的Blog。不論你是否對Blog感到陌生,他們正在不斷地更新自己的Blog頁面,這些頁面就像一個個人日記本一樣。大多數Bloger隻是簡單地記錄每天發生的事情,也有一些聚焦于某一主題,比如影評、球迷組織、電腦技術等。

Blog可以在任何地點由作者進行更新,更新次數可以是一天多次,也可以是一周一兩次。在Blog頁面上隻顯示最近10條更新,但事實上所有的Blog軟體都提供了存檔記錄,訪客可以閱讀其曆史記錄。有了“隐蔽”的網址,Blog應用程式将變得更加強大。假定你通過/2004/02/14.aspx來查詢自己的Blog上的文章,你會為閱讀到2004年2月14日的Blog感到驚訝嗎?此外你可能為了通路2004年2月所有的Blog而将該位址裁減為/2004/02/,要通路 2004年所有的Blog,你可能會試着去通路/2004/。

在維護一個Blog的時候,如果将這種具有“隐蔽性”的網址提供給使用者将會更好。實際上很多Blog引擎都提供了這種網址重寫的功能,現在來看看這些是如何通過網址重寫實作的。

首先,我們需要一個頁面能夠分别按照年、月、日分别顯示Blog的内容。假定現在已經做好了一個頁面檔案ShowBlogContent.aspx,它能分别擷取年、月、日的查詢參數,要檢視2004年2月14日所發的文章,我們可以通路/ShowBlogContent.aspx?year=2004& amp;month=2&day=14,要浏覽2004年2月的資料可以通路/ShowBlogContent.aspx?year=2004& amp;month=2,要查詢2004年所有資料可以通路/ShowBlogContent.aspx?year=2004。(在下載下傳檔案中提供 ShowBlogContent.aspx源代碼。)

然後,當使用者通路/2004/02/14.aspx時,我們需要将他通路的網址重寫到 /ShowBlogContent.aspx?year=2004&month=2&day=14上。這裡需要制定三條網址重寫規則:當指定通路年月日時、當指定通路年月時和當指定通路年時。

在ASP.NET中實作Url Rewriting

< RewriterConfig >

在ASP.NET中實作Url Rewriting

         < Rules >

在ASP.NET中實作Url Rewriting

                 <!--  Rules for Blog Content Displayer  -->

在ASP.NET中實作Url Rewriting

                 < RewriterRule >

在ASP.NET中實作Url Rewriting

                         < LookFor > ~/(d{4})/(d{2})/(d{2}).aspx </ LookFor >

在ASP.NET中實作Url Rewriting

                         < SendTo > ~/ShowBlogContent.aspx?year=$1 &amp; month=$2 &amp; day=$3 </ SendTo >

在ASP.NET中實作Url Rewriting

                 </ RewriterRule >

在ASP.NET中實作Url Rewriting

                 < RewriterRule >

在ASP.NET中實作Url Rewriting

                         < LookFor > ~/(d{4})/(d{2})/Default.aspx </ LookFor >

在ASP.NET中實作Url Rewriting

                         < SendTo > <![CDATA[ ~/ShowBlogContent.aspx?year=$1&month=$2 ]]> </ SendTo >

在ASP.NET中實作Url Rewriting

                 </ RewriterRule >

在ASP.NET中實作Url Rewriting

                 < RewriterRule >

在ASP.NET中實作Url Rewriting

                         < LookFor > ~/(d{4})/Default.aspx </ LookFor >

在ASP.NET中實作Url Rewriting

                         < SendTo > ~/ShowBlogContent.aspx?year=$1 </ SendTo >

在ASP.NET中實作Url Rewriting

                 </ RewriterRule >

在ASP.NET中實作Url Rewriting

         </ Rules >

在ASP.NET中實作Url Rewriting

</ RewriterConfig >

這些網址重寫規則展示了正規表達式的強大威力。第一條規則按照(\d{4})/(\d{2})/(\d{2})\.aspx模式進行查找,通俗的說,它查找是否包含比對xxxx/xx/xx.aspx格式的字元串,其中x表示數字,每一組數字必須用圓括号括起來,這樣可以在相應<SendTo>節内引用圓括号内的比對字元串。我們可以使用$1、$2、$3來分别引用前面比對的圓括号組,其中$1,$2,$3分别表示所比對的第一、第二、第三個圓括号組。

注意:由于web.config是XML格式的文檔,是以在文本域内必須回避直接使用一些特殊字元,如:&,<和>符号等。在第一條網址重寫規則的<SendTo>節中用&amp來表示引用&符号,在第二條網址重寫規則的<SendTo>節中用<![CDATA[...]]>元素來表示其中所有的内容都是文本域,不再需要用轉義字元來表示引用。這兩種方法都可以實作同樣的目的。

下面圖五、圖六、圖七都顯示出網址重寫的運作狀況。這些資料都真實地摘自作者的Blog(http://ScottOnWritting.net),圖五顯示2003年11月7日的文章,圖六顯示所有2003年11月的文章,圖七顯示2003年所有文章。

在ASP.NET中實作Url Rewriting

圖五.顯示2003年11月7日的文章

在ASP.NET中實作Url Rewriting

圖六. 顯示2003年11月所有的文章

在ASP.NET中實作Url Rewriting

圖七. 顯示2003年所有的文章

注意:要使用網址重寫引擎,強烈推薦在<LookFor>節中使用正規表達式。如果你對正規表達式不是很熟悉,可以先閱讀作者本人寫的一篇文章An Introduction to Regular Expressions,此外還可以在RegexLib.com 上查詢常用的正規表達式,或把你自己設計的正規表達式送出到該站點共享使用。

1.14. 建立必須的目錄結構

當IIS接收到對/2004/03/19.aspx的請求時,他發現檔案擴充名.aspx,便将該請求轉交給ASP.NET引擎處理,在ASP.NET 引擎中傳遞時,該位址被重寫到/ShowBlogContent.aspx?year=2004&month=3&day=19,最後使用者将看到該Blog上2004年3月19日所有的文章,但是在使用者通路/2004/03/時會發生什麼呢?除非已經存在一個/2004/01/的目錄,否則IIS将傳回一個404錯誤,而且該目錄下還必須要有一個預設頁面Default.aspx,IIS才能将請求轉交給ASP.NET引擎處理。

通過這種方法你得手動為每一年的Blog建立一個年份的目錄并在該年份下放置一個預設檔案Default.aspx,而且還得在該年份目錄下建立每一月的目錄,從01、02、...、12,每一個目錄下也要防止一個預設檔案Default.aspx。(回想前面的例子,為了将/Products/重寫到 /ListCategories.aspx也是要建立一個/Products/目錄并放置一個預設Default.aspx檔案。

很明顯,這樣建立目錄結構的過程是很痛苦的。解決這種問題的一個辦法就是設定IIS将所有接收的請求都轉交給ASP.NET引擎來處理,這種方法,甚至連通路這種位址 /2004/04/,IIS都如實地将其轉交給ASP.NET引擎處理,這種方法造成ASP.NET引擎得處理所有傳入的請求,包括css檔案,圖檔檔案、Javascript檔案以及Flash檔案等等。

關于對所有類型檔案的處理的詳細讨論已經超出了本書範圍。有關在ASP.NET Web應用程式中使用這些技術的例子請通路 .Text 這個開源的Blog。.Text 可以通過配置将所有請求都轉交給ASP.NET處理。它使用了一個自定義的HttpHandler來處理所有類型的檔案類型,這個自定義的HttpHandler可以識别并判斷如何處理所有的檔案類型。(圖像檔案、CSS檔案等等。)

1.15. 結束語

本文探讨了通過類HttpContext類的RewriteUrl()方法來實作ASP.NET一級的網址重寫,正如我們所看到那樣,RewriteUrl()方法在修改這個特有的HttpContext的Request的屬性時也修改了所請求的檔案和路徑。實際得到的效果就是在使用者通路其特有的網址的時候,他實際卻是在伺服器端請求另一個與此不同的網址。

網址重寫不但可以在HttpModule中執行,也可以在HttpHandler中運作。本文我們探讨了在一個HttpModule中執行網址重寫,也研究了一下網址重寫在ASP.NET中的各個不同場所的情況。

當然,在ASP.NET一級的網址重寫中,隻有在IIS成功地将請求轉交給ASP.NET引擎後才能成功地執行,當使用者請求一個擴充名為.aspx的檔案時這很自然地發生。然而,如果要讓使用者輸入一個實際并不存在的網址,通過網址重寫到另一個存在的aspx頁面,你必須為該請求建立相應的目錄和預設的 Default.aspx頁面,除非配置IIS讓它把所有的請求都轉交給IIS處理,但是這種方式盲目地将所有請求都轉交給了ASP.NET引擎。

1.16. 參考

本文源代碼下載下傳位址:http://download.microsoft.com/download/0/4/6/0463611e-a3f9-490d-a08c-877a83b797cf/MSDNURLRewriting.msi

《深入研究IIS與ASP.NET》(Inside IIS and ASP.NET) 作者:Michele Leroux Bustamante

工作參考:網址重寫是一個頗受歡迎的主題,不論是ASP.NET還是其他競争對手都對其表示出巨大的關注。例如:Apache Web Server提供了一個叫做mod_rewriting的子產品,mod_rewriting是個功能完善的網址重寫引擎,它提供基于HTTP 頭資訊和伺服器參數環境的網址重寫功能,甚至還提供用正規表達式來建立網址重寫規則。有關mod_rewriting更多資訊請參考A User‘s Guide to URL Rewriting with the Apache Web Server《Apache Web Server網址重寫使用者向導》。

這裡有相當數量的關于ASP.NET級别下網址重寫的文章:

Rewrite.NET - A URL Rewriting Engine for .NET探讨模拟mod_rewriting的正規表達式描述的網址重寫規則來實作ASP.NET下網址重寫;

URL Rewriting With ASP.NET提供對ASP.NET下網址重寫能力的總的概述;

Ian Griffiths有一個Blog,上面有許多有關在ASP.NET中實作網址重寫的建議,比如本文所提到的考慮到資料回送時的做法;

Fabrice Marguerie和Jason Salas都各有一個Blog,在上面可以找到有關将ASP.NET網址重寫來推動搜尋引擎的查找替換的文章。

(Fabrice Marguerie: http://weblogs.asp.net/fmarguerie/archive/2003/12/24/45712.aspx)

(Jason Salas: http://weblogs.asp.net/jasonsalas/archive/2003/12/14/43404.aspx)