【
原文地址】Tip/Trick: Url Rewriting with ASP.NET
【原文發表日期】 Monday, February 26, 2007 9:27 PM
經常有人請我指導應該如何動態地“重寫”URL,以在他們的ASP.NETweb應用中發布比較乾淨的URL端點。這個部落格文章概述了幾個方法,你可以用來在ASP.NET中乾淨地映射或重寫URL,以及按照你自己的需求組織你的URL的結構。
為什麼URL映射和重寫很重要?
下面是開發人員想要對URL有更大的靈活性的最常見的情境:
1) 處理這樣的情形:你要更改你的web應用中網頁的結構,但你同時也要確保在你移動網頁後,那些被人收藏的老URL不會成為死連結。重寫URL允許你透明地將請求轉交到新的網頁地址而不出錯。
2) 在象Google,Yahoo 和 Live 這樣的搜尋引擎中提高你網站上網頁的搜尋相關性。具體地來說,URL重寫經常能使你在你網站上網頁的URL裡更加容易地嵌入關鍵詞,這麼做往往會增加別人點擊你的連結的機會。從使用查詢字串參數到使用完全限定(fully qualified)的URL也能在某些情形下提高你在搜尋引擎結果中的優先順序。使用強制referring連結使用同樣的大小寫(same case)和URL入口(譬如,使用weblogs.asp.net/scottgu 而不是 weblogs.asp.net/scottgu/default.aspx)的技術也能避免因跨越多個URL而造成的網頁排名(pagerank)的降低(avoid diluting your pagerank across multiple URLs),從而增加你的搜尋結果。
在一個搜尋引擎日漸驅動網站訪問量的世界裡,在你的網頁排名上稍微得到一些提高就能給你的業務帶來不錯的投資回報(ROI)。逐漸地,這驅使開發人員使用URL重寫以及其他SEO(搜尋引擎最佳化 )技術來最佳化網站(注,SEO是個步調很快的空間,增加你的搜尋相關性的建議月月在演變)。想瞭解一些關於搜尋引擎最佳化方面好的建議的話,我建議你閱讀一下《SSW Rules to Better Google Rankings (SSW的提高Google排名之要領)》,以及MarketPosition關於《how URLs can affect top search engine ranking (URL會如何影響頂級搜尋引擎排名)》的文章。
常式的URL重寫情境
為這個部落格貼子起見,我將假設我們將在一個應用裡建造一套電子商務的產品目錄網頁,產品是按種類來組織的(譬如,圖書,錄影,CD,DVD等等)。
讓我們假定一開始我們有個網頁叫Products.aspx,通過查詢字串參數接受一個類別名稱,相應地過濾顯示的產品。與這個Products.aspx網頁對應類別的URL看上去象這樣:
http://www.store.com/products.aspx?category=books
http://www.store.com/products.aspx?category=DVDs
http://www.store.com/products.aspx?category=CDs
但我們不想使用查詢字串來呈示每個類別,我們想修改應用,讓每個產品類別對搜尋引擎來說看上去象是一個獨特的URL,並且在實際的URL中嵌入關鍵詞(而不是通過查詢字串參數)。我們將在這個部落格文章剩下來的篇幅裡,討論一下達成這個目的我們可以採取的4種不同方法。
方法一:使用Request.PathInfo 參數而不是查詢字串
我將示範的第一個方法根本不使用URL重寫,而是使用ASP.NET中不太為人所知的一個特性,Request的PathInfo屬性。為協助解釋這個屬性的有用之處,考慮一下我們電子商店下面這些URL的情形:
http://www.store.com/products.aspx/Books
http://www.store.com/products.aspx/DVDs
http://www.store.com/products.aspx/CDs
你會在上面這些URL中注意到的一個東西是,他們不再含有查詢字串值,取而代之的是,類別參數的值是附加到URL上的,是以Products.aspx網頁處理器名稱之後的/參數 值的方式出現的。然後,一個自動化的搜尋引擎爬蟲(search engine crawler)會把這些URL解釋成三個不同的URL,而不是一個URL帶有三個不同的輸入值 (搜尋引擎是不理會副檔名的,只把它當作URL中的另一個字元而已)。
你也許很想知道怎麼在ASP.NET中處理這個附加的參數的情形。好訊息是,這非常簡單。只要使用Request的PathInfo屬性就可以了,該屬性返回URL中緊隨 products.aspx 後面的那部分內容。所以,對上面這些URL, Request.PathInfo會分別返回 “/Books”, “/DVDs”,和 “/CDs”(萬一你想知道的話, Request的Path 屬性返回“/products.aspx” )。
然後,你可以輕易地編寫一個函數來擷取產品類別,象這樣(下面這個函數去除前面的斜杠字元,只返回“Books”,“DVDs”,或 “CDs”):
Function GetCategory() As String
If (Request.PathInfo.Length = 0) Then
Return ""
Else
Return Request.PathInfo.Substring(1)
End If
End Function
範例下載:我建立的一個展示這個技術的範例應用可以在這裡下載。這個範例和這個技術的很好的地方在於,為部署使用這個方法的ASP.NET應用,不需作任何伺服器配置改動。在共用主機的環境裡,這個技術也行之有效。
方法二:使用HttpModule實現URL重寫
上述Request.PathInfo技術的替換方法是,利用ASP.NET提供的HttpContext.RewritePath方法。這個方法允許開發人員動態地重寫收到的URL的處理路徑,然後讓ASP.NET使用剛重寫過後的路徑來繼續執行請求。
譬如,我們可以選擇向福士呈示下列URL:
http://www.store.com/products/Books.aspx
http://www.store.com/products/DVDs.aspx
http://www.store.com/products/CDs.aspx
在外界看來,網站上有三個單獨的網頁(對搜尋爬蟲而言,這看上去很棒)。通過使用 HttpContext的RewritePath方法,我們可以在這些請求剛進入伺服器時,動態地把收到的URL重寫成單個Products.aspx網頁接受一個查詢字串的類別名稱或者PathInfo參數。譬如,我們可以使用Global.asax中的Application_BeginRequest事件,來這麼做:
void Application_BeginRequest(object sender, EventArgs e) {
string fullOrigionalpath = Request.Url.ToString();
if (fullOrigionalpath.Contains("/Products/Books.aspx")) {
Context.RewritePath("/Products.aspx?Category=Books");
}
else if (fullOrigionalpath.Contains("/Products/DVDs.aspx")) {
Context.RewritePath("/Products.aspx?Category=DVDs");
}
}
手工編寫象上面這樣的編碼的壞處是,很枯燥乏味,而且容易犯錯。我建議你別自己寫,而是使用網上現成的HttpModule來完成這項工作。這有幾個你現在就可以下載和使用的免費的HttpModule:
- UrlRewriter.net
- UrlRewriting.net
這些模組允許你用聲明的方式在你應用的web.config檔案裡表達匹配規則。譬如,在你應用的web.config檔案裡使用UrlRewriter.Net模組來把上面的那些URL映射到單個Products.aspx頁上,我們只要把這個web.config檔案添加到我們的應用裡去就可以了(不用任何編碼):
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="rewriter"
requirePermission="false"
type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</configSections>
<system.web>
<httpModules>
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>
</httpModules>
</system.web>
<rewriter>
<rewrite url="~/products/books.aspx" to="~/products.aspx?category=books" />
<rewrite url="~/products/CDs.aspx" to="~/products.aspx?category=CDs" />
<rewrite url="~/products/DVDs.aspx" to="~/products.aspx?category=DVDs" />
</rewriter>
</configuration>
上面的HttpModule URL重寫模組還支援Regex和URL模式比對(以避免你在web.config 檔案裡硬寫每個URL)。所以,不用寫死類別名稱,你可以象下面這樣重寫匹配規則,把類別名稱動態地從任何/products/[類別].aspx組合的URL裡取出來:
<rewriter>
<rewrite url="~/products/(.+).aspx" to="~/products.aspx?category=$1" />
</rewriter>
這使得你的編碼極其乾淨,並且擴充性非常之好。
範例下載:我建立的一個使用UrlRewriter.Net模組展示這個技術的範例應用可以在這裡下載。
這個範例和這個技術的很好的地方在於,為部署使用這個方法的ASP.NET應用,不需作任何伺服器配置改動。在設定為中等信任安全等級(medium trust)的共用主機的環境裡,這個技術也行之有效 (只要把檔案FTP/XCOPY到遠程伺服器就可以了,不需要安裝)。
方法三:在IIS7中使用HttpModule 實現無副檔名的URL重寫
上述的HttpModule方法在你要重寫的URL含有.aspx 副檔名或者包含另一個被設定為ASP.NET處理的副檔名的情形下一切都工作。你這麼做的話,不需要任何特定的伺服器配置,你只要把你的應用拷貝到遠程伺服器,它會正常工作的。
但有的時候,你要重寫的URL要麼擁有一個不為ASP.NET處理的副檔名(譬如, .jpg, .gif, 或 .htm),要麼根本沒有副檔名。譬如,我們也許要把這些URL呈示成公開的產品目錄網頁(注意,它們沒有 .aspx 副檔名):
http://www.store.com/products/Books
http://www.store.com/products/DVDs
http://www.store.com/products/CDs
在 IIS5 和 IIS6 中,使用ASP.NET處理上面這樣的URL不是很容易。 IIS 5/6 使得在ISAPI擴充(ASP.NET就是這樣一個擴充)裡非常難以重寫這些類型的URLS。你需要做的是使用ISAPI過濾器在IIS請求管道(request pipeline)的較早期實現重寫。我將在下面的第四個方法裡示範如何在 IIS5/6 實現這樣的重寫。
但好訊息是, IIS 7.0使得處理這類情形容易之極。你現在可以在 IIS 請求管道的任何地方執行一個HttpModule,這意味著你可以使用上面的URLRewriter 模組 來處理和重寫無副檔名的URL(甚至是帶有 .asp,.php,或 .jsp 副檔名的URL)。下面示範了你在IIS7中該如何配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<configSections>
<section name="rewriter"
requirePermission="false"
type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</configSections>
<system.web>
<httpModules>
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" />
</httpModules>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<rewriter>
<rewrite url="~/products/(.+)" to="~/products.aspx?category=$1" />
</rewriter>
</configuration>
注意一下<system.webServer>內<modules>部分設定為true的runAllManagedModulesForAllRequests屬性。這個屬性確保來自Intelligencia的UrlRewriter.Net模組(是在IIS7正式發布前編寫的),會被調用,有機會重寫到伺服器的所有URL請求(包括檔案夾)。上面的web.config檔案非常酷之處在於:
1) 它在任何IIS7機器上都會工作,你不需要管理員在遠程主機上啟用任何東西,它也能在設定為中等信任安全等級(medium trust)的共用主機的環境情境下工作。
2) 因為我在<httpModules>和 IIS7 的<modules> 部分同時配置了UrlRewriter,我既能在 VS內建的web伺服器(即Cassini)中,也能在IIS7下使用同樣的URL重寫規則。兩者完全支援無副檔名的URL重寫。這使得測試和開發非常容易。
IIS 7.0 將在今年的晚些時候作為Windows Longhorn伺服器的一部分發布,將在幾個星期內隨Beta3版本的發布支援go-live許可。由於添加到IIS7中的所有的新宿主(hosting)特性,我們預期主機供應商將會非常快地開始積極提供IIS7帳號,這意味著你應該很快就可以開始利用上述的無副檔名的URL重寫支援。我們將在 IIS7 RTM 時段裡發布一個為微軟所支援的URL重寫模組,該模板是免費的,你可以在IIS7上使用,並且這模組將對你web伺服器上的所有內容的進階URL重寫情境提供很好的支援。
範例下載:我建立的一個使用IIS7和UrlRewriter.Net模組展示無副檔名URL重寫技術的範例應用可以在這裡下載。
方法四:在IIS5和IIS6中使用 ISAPIRewrite 來實現無副檔名的URL重寫
如果你不想等到IIS7出來才利用無副檔名的URL重寫,那麼你最好的措施是使用ISAPI過濾器來重寫URL。我知道有2個ISAPI過濾器方案,你也許要去看一下:
- Helicon Tech's ISAPI Rewrite: 他們提供一個99美元(可免費試用30天)的ISAPI URL重寫產品完整版,以及一個免費的輕量級版本。
- Ionic's ISAPI Rewrite: 這可以免費下載(源碼和可執行檔都可以下載)
我沒親手用過上面的產品,雖然我聽過到對這2個產品的好評。Scott Hanselman和 Jeff Atwood 最近都寫了精彩的部落格貼子講述使用這些產品的體驗,同時提供了一些如何在這些產品中配置匹配規則的例子。Helicon Tech的ISAPI Rewrite的規則使用跟 Apache的mod_rewrite同樣的句法,譬如(取自Jeff的部落格貼子):
[ISAPI_Rewrite]
# fix missing slash on folders
# note, this assumes we have no folders with periods!
RewriteCond Host: (.*)
RewriteRule ([^.?]+[^.?/]) http\://$1$2/ [RP]
# remove index pages from URLs
RewriteRule (.*)/default.htm$ $1/ [I,RP]
RewriteRule (.*)/default.aspx$ $1/ [I,RP]
RewriteRule (.*)/index.htm$ $1/ [I,RP]
RewriteRule (.*)/index.html$ $1/ [I,RP]
# force proper www. prefix on all requests
RewriteCond %HTTP_HOST ^test\.com [I]
RewriteRule ^/(.*) http://www.test.com/$1 [RP]
# only allow whitelisted referers to hotlink images
RewriteCond Referer: (?!http://(?:www\.good\.com|www\.better\.com)).+
RewriteRule .*\.(?:gif|jpg|jpeg|png) /images/block.jpg [I,O]
一定要去讀一下Scott和Jeff的貼子以瞭解這些ISAPI 模組的詳情,以及你都能用它們做些什麼。
註:使用ISAPI過濾器的一個壞處是,共用主機環境一般不允許你安裝這樣的組件,所以你要用它們的話,你要麼需要一個專用的虛擬機器主機伺服器,要麼需要一個專用的主機伺服器。但,如果你有一個主機計劃允許你安裝ISAPI的話,這會在IIS5/6下會提供最大的靈活性,讓你過渡到IIS7推出為止。
在URL重寫裡處理ASP.NET PostBack
大家在使用ASP.NET和重寫URL時經常遇到的一個疑難雜症跟處理postback情境有關。具體地來說,當你在一個網頁上放置一個 <form runat="server"> 控制項時,ASP.NET 會自動地預設輸出標識的action屬性指向當前所在頁面。當使用URL重寫時,會出現這樣的問題,<form> 控制項顯示的URL不是原先請求的URL(譬如,/products/books),而是重寫過後的URL(譬如,/products.aspx?category=books)。這意味著,當你做一個postback到伺服器時,URL不再是你原先乾淨利落的那個了。
在 ASP.NET 1.0 和1.1 中,大家經常訴諸於繼承<form> 控制項產生他們自己的控制項,來正確地輸出要使用的action屬性。雖然這可以工作,但結果有點亂,因為這意味著你需要更新你所有的頁面來使用這個另外的表單控制項,而且有時在Visual Studio所見即所得 (WYSIWYG)設計器裡也會遇上問題。
好訊息是,在ASP.NET 2.0中,有個比較乾淨的訣竅你可以用來重寫<form>控制項的action屬性。具體地來說,你可利用新的ASP.NET 2.0控制項適配器擴充架構來定製控制項的輸出,用你提供的值來覆蓋action屬性的值。這不要求在你的.aspx頁面裡做任何編碼改動,而只要在你的/app_browsers檔案夾裡添加一個.browser檔案,註冊使用一個控制項適配類即可輸出新的action屬性。