熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

在ASP.NET中實現Url Rewriting

2013-11-13 10:13:21  來源: .NET編程 

  概要
  
    分析如何使用微軟提供的來對動態產生的URL地址進行網址重寫 網址重寫是實現一種截取網址請求並將其進行處理後重新指向到一個指定的網址的過程作者本人在對各種實現網址重寫的技術進行研究和探討後得出的經驗和方法希望能對您有所幫助
  
內容簡介
  
    稍微花點時間看一看你做的網站裡頭的URL地址你看到類似這樣的地址嗎x?EmpID=&type=summary ?也許你會出於某種目的把大量的頁面文件從一個目錄甚至一個網站轉移到其他地方而許多訪問者出於個人興趣或者研究目的之前就已經將原有網址收藏了起來 如果這時他從收藏夾打開該頁面的時候發現這已經是壞鏈了本文旨在介紹如何使用網址重寫將那些難看的網址轉換成比較有實際意義的網址使其便於記憶例如將x?EmpID=&type=summary轉換成如下地址 dispEmployeeInfo//l 我們甚至發現 網址重寫技術可以解決令人頭疼的錯誤或者說它可以創建一個智能化的錯誤解決方案
  
    如上所述網址重寫是實現一種截取網址請求並將其進行處理後重新指向到一個指定的網址的過程 在網址重寫執行的期間相應處理程序處理被請求的網址從中提取出相關的值然後重新指向一個新的指定地址例如由於一次網站目錄調整原有的 /people/ 子目錄下的所有網頁全部移動到/info/employees/目錄原訪問者從收藏夾或者其他什麼地方點擊鏈接發出訪問/people/目錄下的文件的請求時你肯定希望他還是能通過原有地址看到和原來相同的頁面但實際上看到的卻是網址重寫指向的新目錄下的相應文件
  
    在老版本ASP中使用網址重寫技術的途徑很少要麼寫一個 ISAPI過濾器要麼購買第三方廠商提供的網址重寫組件然而在微軟提供的ASPNET下你可以通過多種方法很簡單地開發出自己的網址重寫軟件以滿足自己各種不同的需要本文將和你一起討論這門針對ASPNET開發人員的實現網址重寫的技術然後舉一些網址重寫實際應用的例子在我們深入探討網址重寫技術的細節之前我們先看一下日常使用網址重寫技術實現的場景
  
網址重寫的一般用途
  
    創建一個數據操作的ASPNET程序最常見的就是一個aspx頁面後面帶上一些查詢參數集合例如在設計一個電子商務網站的時候假定你設計了一項功能允許用戶浏覽待售的商品為了更加方便操作你設計了一個頁面displayCategoryaspx將商品按照給定的分類顯示那麼該分類下的商品顯示頁面上應該在頁面文件對應網址後面加上了一個商品分類的查詢參數例如用戶要查詢待售的裝飾品在數據庫中所有的裝飾品數據對應的分類編號CategoryID的值為那麼用戶會訪問如下網址x?CategoryID=
  
    創建一個包含類似這樣網址的網站最終有兩種結果首先從最終用戶的角度來觀察x?CategoryID= 這個網址有些雜亂 可行性分析專家Jakob Neilson(主頁 ) 建議選擇網址顯示方式時候考慮如下要求(參考網址
  
    · 是否簡短
  
    · 是否易於輸入
  
    · 是否將站點結構形象化
  
    · 是否具有隱蔽性也就是讓用戶通過一個虛擬的看似有意義的導航地址訪問指向該地址
  
    我想還應該在上述列表中再增加一條 是否便於記憶x?CategoryID= 這個地址沒有一個地方符合Neilson標准的任何一條也不便於記憶當然對於有經驗的網絡開發專家來說他們很熟悉這種鍵值對構成的查詢參數結構體系然而對於普通用戶來說輸入這些帶有參數的網址實在是太麻煩了
  
    一種較好的方法就是使用一種比較直觀且容易記憶的方式來將網址表示為 乍一看很容易就會推斷這個網址所對應的內容極有可能會是顯示裝飾品(Widgets)信息這個網址就變得更加容易記憶和傳播!然後我告訴我的同事請查看這個網址 不用我說第二遍她可能一次就把地址敲到浏覽器上了(你也可以在亞馬遜()的網站上這樣嘗試一下)很快就浏覽器上就列出了裝飾品(Widgets)的內容這裡隱蔽性表示用戶可以自行變更網址的結尾例如輸入 就能看到全部分類相關的商品列表或者列出所有相關商品分類目錄列表
  
    注用上述簡單的變更網址內容的方法來構思一下如今的比較流行的Blog網站生成的網址例如要查詢日所發的帖子只需輸入 即可如果將網址裁減為 則顯示 月份的帖子 同樣將月份裁減掉得到 則顯示出年全年所發的帖子
  
    網址重寫技術除了用於將復雜的網址簡單化之外它還能用於處理因網站目錄調整或者其他原因導致產生大量的無效鏈接和過期書簽
  
當一個Web請求傳送到IIS會發生什麼?
  
    在探討如何實現網址重寫這項技術之前很有必要了解一下IIS是處理所接收的Web請求的機制 當一個Web請求到達IIS Web服務器時IIS會根據所請求的文件後綴名來決定如何處理該請求IIS可以處理諸如HTML頁面圖片靜態內容或者將請求轉發給ISAPI應用程序由該ISAPI應用程序處理後生成HTML靜態內容返回給IIS最後由IIS將請求結果發送回給客戶端(一個ISAPI應用程序就是一套編譯好能隨時在後台運行的類庫它的任務就是根據請求生成相關的內容
  
    例如如果IIS接收到一個對Infoasp的請求它會將該請求轉交給 aspdll來處理該ISAPI應用程序調出並執行所請求的ASP頁面然後把生成的HTML代碼返回給IISIIS最後把內容發送回請求客戶端對於ASPNET頁面IIS則將請求轉交給名為 aspnet_isapidll的ISAPI應用程序來處理該ISAPI應用程序 調用托管的ASPNET工作進程來處理該請求並將生成的HTML代碼返回給請求客戶端
  
    你可以自定義IIS 將某一類擴展名映射到指定的ISAPI應用程序圖一顯示了IIS管理工具中的應用程序配置對話框
  
  圖一.已配置的文件擴展名映射
  
    關於對IIS如何管理所接收的請求的詳細探討有些超出本文內容重要的是要了解 ASPNET引擎只負責處理對擴展名已經被正確配置映射到aspnet_isapidll的網絡請求

   

  用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)

  本文所探討的用ASPNET實現的網址重寫技術就是 基於ISAPI過濾器用於網址重寫的技術內容然而我們仍然要討論一下究竟是使用ISAPI過濾器還是使用ASPNET應用程序提供的技術來實現網址重寫技術

  當一個請求傳入ASPNET引擎的時候會發生什麼?

  ASPNET問世之前在IIS Web服務器上的網址重寫功能需要通過ISAPI過濾器來實現自從這個家伙問世後我們就能通過ASPNET來實現URL重寫了因為ASPNET的解釋引擎與IIS有極大的相似之處產生這些相似性主要是因為 ASPNET

  · 在處理接收的請求的生命期內也會產生事件

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

  · 將請求的資源委托給HttpHandler處理這與IIS中的ISAPI應用程序類似

  和IIS一樣在一個請求的整個生命期內ASPNET對該請求的處理狀態發出的狀態改變信號引發相應的事件例如BeginRequest事件在ASPNET開始響應客戶端請求之始引發AuthenticateRequest事件在ASPNET確立用戶身份後引發當然還有諸如AuthorizeRequestResolveRequestCacheEndRequest等其它很多事件這些 都是SystemWebHttpApplication類下的事件更多信息請參考技術文檔中的類HttpApplication概要

  如上所述 可以創建ISAPI過濾器並用於相應IIS引發的事件同理ASPNET也提供了HttpModule用於響應ASPNET引擎引發的事件一個ASPNET應用程序 通過配置可以擁有多個HttpModuleASPNET引擎 每處理一個請求便初始化一個相應配置好的HttpModule並允許它 針對請求處理期間引發的事件生成相應的事件委托事實上ASPNET引擎 處理每一個請求調用大量的事件委托FormsAuthenticationModule就是眾多內嵌HttpModule中的一個它 首先檢查是否使用表單授權如果是的話它 將檢查用戶是否已授權如果沒有授權則自動把用戶重定向到指定的登錄頁面(即在中可以直接記錄並判別用戶登錄授權的問題了!)

  回憶在IIS中一項請求最後被轉交給一個ISAPI應用程序處理該應用程序針對每一項請求進行處理並返回相應的數據例如客戶端發出一個訪問經典ASP頁面的請求IIS將該請求轉交給aspdll程序處理aspdll針對該請求執行asp頁面內容並返回HTML編碼ASPNET也使用了類似的手法ASPNET引擎在將這些HttpModule初始化後判斷並決定調用相應的HttpModule來處理該請求(問怎麼程序操作httpModule)

  所有通過ASPNET引擎解析的請求最終被送交一個HttpHandler或者HttpHandlerFactory(一個HttpHandler只是簡單地返回一個用於處理該請求的HttpHandler的實例)最終的委托呈現並響應所請求的HTML編碼並發送回IISIIS則將HTML返回給請求客戶端
ASPNET包含許多HttpHandler例如PageHandlerFactory是用於呈現ASPNET頁面內容WebServiceHandlerFactory用於呈現ASPNET Web服務的SOAP數據包TraceHandler用於將ASPNET請求資源的HTML標記寫入traceaxd

  圖二描繪了一個針對ASPNET資源的請求所經過的處理流程首先IIS接收到該請求並將其轉交給aspnet_isapidll其次ASPNET引擎將一些HttpModule初始化最後最終的HttpHandler被調用生成相應的標記語言並將其返回給IIS最終返回到請求客戶端

按此在新窗口浏覽圖片
圖二.IIS和ASPNET對請求的處理過程

  創建並注冊自定義HttpModule和HttpHandler

  創建自定義HttpModule的工作相對較簡單它包括一個實現當前接口的托管類HttpModule必須實現SystemWebIHttpModule接口同樣HttpHandlerHttpHandlerFactory必須分別實現SystemWebIHttpHandler接口和SystemWebIhttpHandlerFactory接口有關創建HttpHandlerHttpModule的細節已經超出本書范圍

  一旦HttpModuleHttpHandler被創建後必須向Web應用程序注冊如果要 向整個Web服務器HttpModuleHttpHandler只需簡單的寫入nfig文件如果是 由指定的Web應用程序調用則需在該程序的nfig配置文件中添加幾行XML標記

  例如要向指定的Web應用程序注冊HttpModuleHttpHandler只需向該Web應程序的nfig配置文件中configuration\SystemWeb節中添加下列幾行



<HttpModules>
<add type=type name=name />
</HttpModules>

  其中type屬性為HttpModule的標識號和類庫名稱name屬性則為該模塊取一個較為友好的名稱方便 在Globalasax調用
HttpHandlerHttpHandlerFactory則是 在nfig文件中configuration\SystemWeb節中添加<httpHandler>標記例如

<httpHandlers>
<add verb=verb path=path type=type />
</HttpModules>

  回憶上文 ASPNET對每一個接收到的請求指派相應的HttpHandler來處理並呈現相應內容該指派決定於所接收請求的verb和path的內容verb為HTTP請求的類型GET或者POSTpath則為請求的文件的路徑和文件名如果我們打算用一個HttpHandler來處理所有GET類型和POST類型的並且文件擴展名為scott的內容可以在nfig相應配置節中加入下列標記

<httpHandlers>
<add varb=* path=scott type=type />
</httpHandlers>

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

  注意在注冊HttpHandler的時候必須注意HttpHandler所使用的文件擴展名必須已經在IIS中做指向ASPNET引擎的映射在上面scott擴展名的例子中如果我們所使用的scott擴展名如果沒有在IIS中做指向ASPNET引擎的映射的話假定對fooscott文件發出請求該請求 將導致IIS將fooscott文件內容直接呈現給客戶端為了能夠讓HttpHandler處理該請求必須將scott擴展名在IIS中做指向ASPNET引擎的映射之後IIS才能正確地將scott的請求轉交給相應的HttpHandler

  實現網址重寫

  網址重寫技術不但可以在IIS Web服務器一級通過ISAPI過濾器實現而且還可以在ASPNET一級通過HttpModule或者HttpHandler實現本文主要關注在ASPNET一級實現網址重寫技術所以此時不必關注在ISAPI應用程序中實現網址重寫的技術細節而且有很多第三方廠商提供的ISAPI過濾器

構建網址重寫引擎

  在ASPNET中實現網址重寫很簡單 只需調用SystemWebHttpContext類的RewritePath()方法即可HttpContext類中包含有關於特定HTTP請求的HTTP規范信息ASPNET引擎每接收到一個特定請求後便針對該請求創建一個特定的實例這個類包含一些屬性諸如 RequestResponse屬性分別提供對請求和響應的訪問ApplicationSession屬性提供對Application變量和Session變量的訪問User屬性提供對已授權用戶信息的訪問

  在微軟NET Framework 版本中RewritePath()方法接收一個新路徑的簡單字符串在其內部HttpContext類的RewritePath(string)方法內在地更新Request對象的路徑和查詢參數除了RewritePath(string)方法之外NET Framework 版還提供了另外一些重載版本其中一個重載版本接收三個輸入字符串參數這種交替的重載形式不僅僅只是設置Request對象的路徑和查詢參數這些屬性而是設置更深層的成員變量這些成員變量用於為 PhysicalPathPathInfoFilePath屬性計算Request對象值

  為了實現ASPNET中的網址重寫我們需要創建一個HttpHandlerHttpModule用於

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

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

  以前文所構建的那個站點為例可以通過/info/employeeaspx?empID=EmployeeID來訪問每一個雇員的信息為了使這個網址更加地具有隱蔽性我們可能會使用更加容易理解的訪問方式如/people/雇員名aspx這裡就有了一個網址重寫的案例當接收到對/people/ScottMitchellaspx的請求的時候我們就得使用網址重寫使得對該頁面的請求被重寫指向到先前使用的/info/employee?EmpID=地址

  使用HttpModule來調用網址重寫

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

   HttpModule 事件 簡介 FormsAuthenticationModule AuthenticateRequest 判斷用戶是否已通過表單授權方式獲取授權如果沒有的話則將用戶重定向到指定的登錄頁面 FileAuthorizationModule AuthorizeRequest 當使用Windows授權方式的時候HttpModule判斷並確定該Microsoft Windows帳戶是否對其請求的資源擁有足夠的權限 UrlAuthorizationModule AuthorizeRequest 檢查並確認請求者是否對所訪問的網址擁有權限該Url授權可以在nfig文件的<authorization>和<location>元素中配置

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

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

  如果把網址重寫放在BeginRequest事件或者AuthenticateRequest事件中在登錄頁面上執行提交後該頁面會將用戶重定向到網址重寫指定的頁面假定當用戶在浏覽器上敲入/people/ScottMitchellaspx地址該地址是要被重定向到/info/employeeaspx?EmpID= 如果該Web應用程序設定使用表單驗證當用戶開始訪問/people/ScottMitchellaspx的時候該網址將重寫指向/info/employeeaspx?EmpID=接著ForumAuthenticationModule啟動如果需要的話將用戶重定向到登錄頁面用戶登錄後重定向到的頁面將是/info/employeeaspx?EmpID=這也是自從FormAuthenticationModule啟動運行時所發出請求的頁面
同上類似當把網址重寫放在BeginRequest事件或者AuthenticateRequest事件中運行的時候UrlAuthenticationModule也發現了網址重寫指向的網址這意味著如果在該應用程序的nfig文件中<location>節為特定的網址配置特定的授權地址的話你得引用重寫所指向的網址

  為了解決這個微妙的問題一個可能就是把網址重寫放在AuthorizeRequest事件中運行但是在使用這種方法解決URL授權和表單授權的異常時又引入了一個新的缺陷文件授權會失效當使用Windows驗證的時候FileAuthorizationModule檢查並驗證已通過驗證的用戶是否擁有足夠的權限訪問特定的ASPNET頁面

  假定有一群用戶並沒有Windows級別的訪問權限訪問C:\inetpub\wwwroot\info\employeeaspx當這些用戶試圖訪問/info/employeeaspx?EmpID=的時候他們會得到未授權的錯誤如果我們把網址重寫放到AuthenticateRequest事件中運行FileAuthorizationModule驗證該安全性設置的時候他仍任人為被請求的文件是/people/ScottMitchellaspx而這時該網址已經被重寫了因此FileAuthorizationModule會直接放行讓用戶看到了網址重寫指向的內容/info/employeeaspx?Empid=

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

  使用HttpHandler來調用網址重寫

  除了上面所述方法外網址重寫也可以放入HttpHandler或者HttpHandlerFactory中調用HttpHandler是一個負責針對特定請求生成相應內容的類HttpHandlerFactory返回一個HTTP的實例該實例針對特定請求生成相應內容

  本節將著眼於為這些ASPNET頁面創建一個網址重寫的HttpHandlerFactory創建HttpHandlerFactory必須實現IHTTPHandlerFactory接口它包括一個GetHandler()方法ASPNET引擎在初始化這些HttpModule後做出決定針對該請求調用相應的HttpHandler或者HttpHandlerFactory在調用HttpHandlerFactory的時候針對該Web請求以及隨同的其他信息的HttpContext中經過的的HttpHandlerFactoryGetHandler()方法將被ASPNET引擎調用HttpHandlerFactory必須返回一個能委托該請求的對象並且該對象要能實現IHttpHandler接口

  要通過一個HttpHandler來調用網址重寫可以先創建一個HttpHandlerFactory它的GetHandler()方法檢查所請求的網址並決定是否需要調用網址重寫如果要調用網址重寫的話則調用前文所述的已通過檢查的HttpContext對象的RewritePath()方法最後該HttpHandlerFactory返回一個由類SystemWebUIPageParserGetCompiledInstance()方法返回的HttpHandler(這與內嵌於ASPNET頁面的HttpHandlerFactoryPageHandlerFactory)的工作原理相同

  在所有HttpModule被初始化後HttpHandlerFactory就開始被實例化把網址重寫放在這些事件場所的最後一個裡頭調用的時候也會碰到相同的問題文件授權將會失效如果非要依賴於Windows驗證和文件驗證的時候你可能得使用HttpModule來調用網址重寫了
下一章我們著眼於如何構建一個可重用的網址重寫引擎使用下文所提的這些示例均以真實案例作為參照在作者主頁上提供下載先用用一個簡單的網址重寫的例子來探討如何實現網址重寫緊接著將利用網址重寫引擎中正則表達式的強大處理能力來展示真正隱蔽的網址重寫技術!

使用網址重寫引擎實現簡單的網址重寫

  為了便於在Web應用程序中實現網址重寫我構建了一個網址重寫引擎該引擎提供下列功能

  ·可以在nfig文件中為頁面開發者定義其所使用的網址重寫引擎的規則

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

  ·能夠通過簡單配置即可在HttpModuleHttpHandler中使用網址重寫

  本節只探討通過HttpModule來實現網址重寫要了解如何通過HttpHandler來實現網址重寫請下載本文提供的代碼

設置網址重寫引擎的配置信息

  我們來探討一下在nfig中網址重寫規則的配置節首先必須在nfig文件中指出是否需要在HttpHandler或者HttpModule中調用網址重寫在nfig中下文已經包含了兩個已經被注釋掉的配置節

<!
<HttpModules>
<add type=URLRewriterModuleRewriterURLRewriter name=ModuleRewriter/>
</HttpModules>


<!
<httpHandlers>
<add verb=* path=*aspx type=URLRewriterRewriterFactoryHandlerURLRewriter />
</httpHandlers>


  被注釋掉的<HttpModules>為配置使用HttpModule調用網址重寫注釋掉的<httpHandler>為配置使用HttpHandler調用網址重寫
不論配置使用<HttpModules>還是<httpHandlers>調用網址重寫除此之外還須配置網址重寫規則一條重寫規則包括兩項字符串請求URL中的查找模式和針對該模式的匹配成功後的替換字符串該信息在nfig文件中用下列標簽描述

  <RewriterConfig>
<Rules>
<RewriterRule>
<LookFor>pattern to look for</LookFor>
<SendTo>String to replace pattern with </SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>pattern to look for</LookFor>
<SendTo>String to replace pattern with </SendTo>
</RewriterRule>

</Rules>
</RewriterConfig>

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

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

使用HttpModule來執行網址重寫

  創建一個HttpModule很簡單只要創建一個實現IHttpModule接口的類IHttpModule接口定義了兩個方法

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

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

  為了更加方便地為網址重寫創建HttpModule從一開始我就創建一個抽象的基類(BaseModuleRewriter該類實現了IHttpModule接口Init(HttpApplication)事件中它通過BaseModuleRewriter_AuthorizeRequest方法引發了HttpApplicationAuthorizeRequest事件BaseModuleRewriter_AuthorizeRequest方法通過該類的Rewrite()方法重寫傳入參數HttpApplication對象的內部請求虛擬路徑(PathBaseModuleRewriter對象中Rewrite()方法是抽象的並且沒有實際內容但在繼承自該類的對象中必須重載Rewrite()方法並為該方法提供實際內容

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

public abstract class BaseModuleRewriter : IHttpModule
{
public virtual void Init(HttpApplication app) {
// WARNING! This does not work with Windows authentication!
// If you are using Windows authentication
// change to appBeginRequest
appAuthorizeRequest += new EventHandler(thisBaseModuleRewriter_AuthorizeRequest);
}

public virtual void Dispose() {}

protected virtual void BaseModuleRewriter_AuthorizeRequest(object sender EventArgs e) {
HttpApplication app = (HttpApplication) sender;
Rewrite(appRequestPath app);
}

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

  注意BaseModuleRewriter類將網址重寫放在AuthorizeRequest事件中調用如果要使用Windows驗證並使用文件驗證模式時請修改代碼將網址授權放在BeginRequest或者AuthenticateRequest事件中

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

protected override void Rewrite(string requestedPath SystemWebHttpApplication app)
{
// get the configuration rules
RewriterRuleCollection rules = RewriterConfigurationGetConfig()Rules;
 // iterate through each rule
for(int i = ; i < rulesCount; i++)
{
// get the pattern to look for and
// Resolve the Url (convert ~ into the appropriate directory)
string lookFor = ^ +
RewriterUtilsResolveUrl(appContextRequestApplicationPath rules[i]LookFor) + $;
// Create a regex (note that IgnoreCase is set)
Regex re = new Regex(lookFor RegexOptionsIgnoreCase);
// See if a match is found
if (reIsMatch(requestedPath))
{
// match found do any replacement needed
string sendToUrl = RewriterUtilsResolveUrl(appContextRequestApplicationPath reReplace(requestedPath rules[i]SendTo));
// Rewrite the URL
RewriterUtilsRewriteUrl(appContext sendToUrl);
break; // exit the for loop
}
}
}

  該Rewriter()方法以獲取nfig文件中的網址重寫規則的設置為起始它通過循環訪問各條網址重寫規則每次均獲取當前規則中的LookFor屬性用正則表達式驗證並判斷是否查找是否對當前請求的網址是否有匹配

  如果發現一條匹配將用當前規則的SendTo值對請求的路徑執行一個正則表達式替換替換後的地址通過參數的形式傳給RewriterUtilsRewriteUrl()方法RewriterUtils是一個幫助類它提供一對HttpModuleHttpHandler都可以使用的靜態方法RewriterUrl()方法只是簡單地調用了HttpContext對象的RewritePath()方法

  注意你已經注意到了當執行正則表達式匹配和替換的時候調用了一個RewriterUtilsResolveUrl()方法該幫助方法簡單地替換了應用程序路徑中~的所有實例

  我們已經探討了主要的部分但是還有其它一些組件諸如將nfig文件中XML格式化了的網址重寫規則反序列化至一個對象的類定義通過HttpHandlerFactory實現網址重寫的類定義等本文最後三節將通過一些真實案例來探討網址重寫的技術

用網址重寫引擎實現簡單的網址重寫

  為了更好地示范網址重寫引擎的運行我們來建立一個ASPNET Web應用程序來實現簡單的網址重寫引擎假定我們為一家在線銷售各類商品的公司服務這些產品劃分為以下類別

  分類編號(CategoryID) 分類名稱(CategoryName)
             飲料(Beverages)
             調味品(Condiments)
             工藝品(Confections)
             日記本(Diary Products)
   
  
  假定已經建立好一個名為ListProductsByCategoryIDaspx的ASPNET頁面文件它通過查詢參數獲取一個分類編號並根據此編號獲取所有該分類下的所有商品如果用戶想浏覽所銷售的飲料類商品可以通過ListProductsByCategoryIDaspx?CategoryID=來訪問如果用戶想浏覽所銷售的日記本類商品可以通過ListProductsByCategoryIDaspx?CategoryID=來訪問假定還有一個頁面ListCategoriesaspx它列出所有代售商品的分類編號

  顯然這裡發現了一個網址重寫的案例對於用戶來說他們所輸入的地址不具有任何實際意義並且不具備任何隱蔽性倒不如使用網址重寫引擎讓用戶去訪問/Products/Baverageaspx地址系統將該地址重寫到ListProductsByCategoryIDaspx?CategoryID=我們可以在nfig文件中來完成網址重寫任務

<RewriterConfig>
<Rules>
<! — Rules for products lister
<RewriterRule>
<LookFor>~/Products/Baverageaspx</LookFor>
<SendTo>~/ListProductsByCategoryIDaspx?CategoryID=</SendTo>
</RewriterRule>
</Rules>
</RewriterConfig>

  很明顯地看到搜索用戶訪問的路徑是否匹配/Products/Baverageaspx如果匹配的話則將網址重寫到/ListProductsByCategoryIDaspx?CategoryID=

  注意你會發現<LookFor>節點中避免直接在Baverageaspx中使用句點是因為<LookFor>節點的值是正則表達式的匹配模式在正則表達式中句點符號是一個特殊字符它表示匹配任何一個字符也就是說如果訪問BaverageQaspx時也會發生匹配為了避免發生這個句點引起的匹配我們得在該句點符號前面加上一個\表示引用句點符號

  通過該規則定義當用戶訪問/Products/Baverageaspx文件的時候他們將看到代售的飲料類商品列表信息為訪問/Products/Baverageaspx地址時的浏覽器截圖注意在浏覽器中地址欄上顯示的是用戶輸入的/Products/Baverageaspx地址但是實際訪問的地址卻是網址重寫後的/ListProductsByCategoryIDaspx?CategoryID=(事實上在服務器上根本就不存在/Products/Baverageaspx文件!)

按此在新窗口浏覽圖片
圖三.網址重寫後的對商品分類的請求

  和/Products/Baverageaspx類似下一步我們添加其它分類的重寫規則只需簡單地在nfig文件中<Rules>中在添加其他<RewriteRule>節即可該演示完整的重寫規則集合請參考下載文檔的nfig文件中的定義

  為了讓該網址更具有隱蔽性如果讓用戶把/Products/Baverageaspx後面Baverageaspx一段截去在浏覽器中輸入/Products/來浏覽產品分類列表會更好一些乍一看這項任務微不足道只需添加一條網址重寫規則將/Products/映射到/ListCategoriesaspx即可然而這裡有一個微妙之處你必須先創建一個/Products/目錄並在裡面放一個空文件Defaultaspx

  要認識為什麼這些額外的步驟是必須的先回顧一下前文網址重寫引擎是處於ASPNET一級的也就是說如果ASPNET沒有獲得處理請求的機會的話網址重寫引擎就不能對輸入的網址請求作出判斷此外IIS僅在請求文件包含相應擴展名時才將請求轉交給ASPNET引擎如果用戶訪問/Products/IIS並不知道其擴展名是什麼於是它檢查該目錄下的文件看是否包含有默認首頁文件名(DefaultaspxDefaultasp等等這些文件名在IIS管理工具對話框中Web服務器屬性對話框中的文檔標簽中定義)當然如果/Products/目錄不存在的話IIS將返回一個HTTP 錯誤

  所以我們需要創建一個/Products/目錄並在該目錄下額外創建一個空文件DefaultaspxIIS會檢查該目錄下的文件發現有一個默認文件名Defaultaspx於是將請求轉交給ASPNET這樣網址重寫引擎才能生效

<RewriterRule>
<LookFor>~/Products/Defaultaspx</LookFor>
<SendTo>~ListCategoriesaspx</SendTo>
</RewriterRule>

  通過該規則用戶訪問/Products/Defaultaspx或者訪問/Products/都可以看到如圖四所示的產品分類列表

按此在新窗口浏覽圖片
圖四在網址上添加隱蔽性

  處理回送數據

  如果要重寫的網址上包含有服務器端Web Form並執行數據回送當該Web Form回送數據時會暴露出真實的網址也就是說當用戶訪問/Products/Baverageaspx時浏覽器上地址欄顯示的也是/Products/Baverageaspx但是實際上是訪問/ListProdutsByCategoryIDaspx?CategoryID=的內容如果ListProductsByCategoryIDaspx頁面執行了數據回送的話用戶被數據回送定向給原始的/ListProductByCategoryIDaspx?CategoryID=頁面上而不是/Products/Baverageaspx頁面這雖然不是什麼大問題但是用戶會覺察到點擊一個按鈕時網址發生了的變化這也許會令人不安因為如果出於網址安全的角度來說直接把真實的網址暴露出來了
之所以發生這種現象的原因是當Web Form在呈現之時就明確地設置其action屬性為當前Request對象中文件路徑的值當然在Web Form呈現之時從/Produts/Baverageaspx到/ListProductsByCategoryIDaspx?CategoryID=的網址重寫就已經執行完畢了這意味著Request對象所匯報的是當前用戶所訪問的地址是/ListProductsByCategoryIDaspx?CategoryID=這麼看來只需讓該服務器端表單在呈現之時不呈現action屬性即可解決問題了(對浏覽器來說如果不設置action屬性的話那麼在提交的時候將使用其默認值

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

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

namespace ActionlessForm
{
public class Form:SystemWebUIHtmlControlsHtmlForm
{
protected override void RenderAttributes(SystemWebUIHtmlTextWriter writer)
{
writerWriteAttribute(namethisName);
baseAttributesRemove(name);

writerWriteAttribute(methodthisMethod);
baseAttributesRemove(method);

thisAttributesRender(writer);
baseAttributesRemove(action);
if (baseID!=null)
{
writerWriteAttribute(idthisClientID);
}
}
}
}

  對RenderAttributes()方法重載的代碼包含了原類HtmlFormRenderAttributes()方法全部的代碼內容只是簡單地去掉了設置action屬性這一節

  當創建並編譯了這個類後將其添加到引用目錄即可在該ASPNET Web應用程序中使用為了將原有HtmlForm類替換只需簡單地在頁面頂部添加下列代碼

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

  然後將<Form runat=server>標簽替換為

<skm:Form id=Form method=post runat=Server

  並將結束標記</Form>替換為

<skm:Form>

  你可以查看該文檔相關下載中的ListProductsByCategoryIDaspx文件中的自定義Web Form該下載已經提供了完整的Visual StudioNET項目文件包

  注意如果你打算進行網址重寫的地址不執行數據回送則沒有必要使用該自定義Web Form的類

  創建真正隱蔽的網址

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

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

  Blog可以在任何地點由作者進行更新更新次數可以是一天多次也可以是一周一兩次在Blog頁面上只顯示最近條更新但事實上所有的Blog軟件都提供了存檔記錄訪客可以閱讀其歷史記錄有了隱蔽的網址Blog應用程序將變得更加強大假定你通過///aspx來查詢自己的Blog上的文章你會為閱讀到日的Blog感到驚訝嗎?此外你可能為了訪問月所有的Blog而將該地址裁減為///要訪問年所有的Blog你可能會試著去訪問//

  在維護一個Blog的時候如果將這種具有隱蔽性的網址提供給用戶將會更好實際上很多Blog引擎都提供了這種網址重寫的功能現在來看看這些是如何通過網址重寫實現的

  首先我們需要一個頁面能夠分別按照年日分別顯示Blog的內容假定現在已經做好了一個頁面文件ShowBlogContentaspx它能分別獲取年日的查詢參數要查看日所發的帖子我們可以訪問/ShowBlogContentaspx?year=&month=&day=要浏覽月的數據可以訪問/ShowBlogContentaspx?year=&month=要查詢年所有數據可以訪問/ShowBlogContentaspx?year=(在下載文件中提供ShowBlogContentaspx源代碼

  然後當用戶訪問///aspx時我們需要將他訪問的網址重寫到/ShowBlogContentaspx?year=&month=&day=這裡需要制定三條網址重寫規則當指定訪問年月日時當指定訪問年月時和當指定訪問年時

<RewriterConfig>
<Rules>
<! Rules for Blog Content Displayer
<RewriterRule>
<LookFor>~/(d{})/(d{})/(d{})aspx</LookFor>
<SendTo>~/ShowBlogContentaspx?year=$&month=$&day=$</SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>~/(d{})/(d{})/Defaultaspx</LookFor>
<SendTo><![CDATA[~/ShowBlogContentaspx?year=$&month=$]]></SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>~/(d{})/Defaultaspx</LookFor>
<SendTo>~/ShowBlogContentaspx?year=$</SendTo>
</RewriterRule>
</Rules>
</RewriterConfig>

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

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

  下面圖五圖六圖七都顯示出網址重寫的運行狀況這些數據都真實地摘自作者的Blog圖五顯示日的帖子圖六顯示所有月的帖子圖七顯示年所有帖子

按此在新窗口浏覽圖片
圖五顯示日的帖子

按此在新窗口浏覽圖片
圖六 顯示月所有的帖子

  按此在新窗口浏覽圖片
圖七 顯示年所有的帖子

  注意要使用網址重寫引擎強烈推薦在<LookFor>節中使用正則表達式

  創建必須的目錄結構

  當IIS接收到對///aspx的請求時他發現文件擴展名aspx便將該請求轉交給ASPNET引擎處理在ASPNET 引擎中傳遞時該地址被重寫到/ShowBlogContentaspx?year=&month=&day=最後用戶將看到該Blog上日所有的帖子但是在用戶訪問///時會發生什麼呢?除非已經存在一個///的目錄否則IIS將返回一個錯誤而且該目錄下還必須要有一個默認頁面DefaultaspxIIS才能將請求轉交給ASPNET引擎處理

  通過這種方法你得手動為每一年的Blog創建一個年份的目錄並在該年份下放置一個默認文件Defaultaspx而且還得在該年份目錄下創建每一月的目錄每一個目錄下也要防止一個默認文件Defaultaspx(回想前面的例子為了將/Products/重寫到/ListCategoriesaspx也是要建立一個/Products/目錄並放置一個默認Defaultaspx文件

  很明顯這樣創建目錄結構的過程是很痛苦的解決這種問題的一個辦法就是設置IIS將所有接收的請求都轉交給ASPNET引擎來處理這種方法甚至連訪問這種地址///IIS都如實地將其轉交給ASPNET引擎處理這種方法造成ASPNET引擎得處理所有傳入的請求包括css文件圖片文件Javascript文件以及Flash文件等等

  關於對所有類型文件的處理的詳細討論已經超出了本書范圍有關在ASPNET Web應用程序中使用這些技術的例子請訪問Text 這個開源的BlogText 可以通過配置將所有請求都轉交給ASPNET處理它使用了一個自定義的HttpHandler來處理所有類型的文件類型這個自定義的HttpHandler可以識別並判斷如何處理所有的文件類型(圖像文件CSS文件等等

結束語

  本文探討了通過類HttpContext類的RewriteUrl()方法來實現ASPNET一級的網址重寫正如我們所看到那樣RewriteUrl()方法在修改這個特有的HttpContextRequest的屬性時也修改了所請求的文件和路徑實際得到的效果就是在用戶訪問其特有的網址的時候他實際卻是在服務器端請求另一個與此不同的網址

  網址重寫不但可以在HttpModule中執行也可以在HttpHandler中運行本文我們探討了在一個HttpModule中執行網址重寫也研究了一下網址重寫在ASPNET中的各個不同場所的情況

  當然在ASPNET一級的網址重寫中只有在IIS成功地將請求轉交給ASPNET引擎後才能成功地執行當用戶請求一個擴展名為aspx的文件時這很自然地發生然而如果要讓用戶輸入一個實際並不存在的網址通過網址重寫到另一個存在的aspx頁面你必須為該請求創建相應的目錄和默認的Defaultaspx頁面除非配置IIS讓它把所有的請求都轉交給IIS處理但是這種方式盲目地將所有請求都轉交給了ASPNET引擎


From:http://tw.wingwit.com/Article/program/net/201311/12964.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.