轉自周公的博客!
《ASP.NET夜話》第十二章中探討過ASP.NET底層運行機制的問題,在該書中本人也講到過了解一些ASP.NET的低層機制對於我們靈活控制ASP.NET有很大幫助,在該書中本人講述過如何用自定義的IHttpHandler來實現防盜鏈功能,由於篇幅限制在該書中沒有講述自定自定義IHttpModule可以實現什麼樣的效果,在本篇將講述利用自定義IHttpModule來實現URL地址重寫。
一般來說,要顯示一些動態數據總是採用帶參數的方式,比如製作一個UserInfo.aspx的動態頁面用於顯示系統的UserInfo這個用戶信息表的數據,那麼需要在其後帶上一個參數來指定要顯示的用戶信息,比如UserInfo.aspx?UserId=1用於顯示錶中編號爲1的用戶的信息,如果爲2則顯示錶中編號爲2的用戶信息。在一些系統中我們可能看到的不是這樣的效果,可能會看到形如UserInfo2.aspx這樣的形式(當然形式可以多樣,只要有規律就行),當點擊這樣一個鏈接時看到的效果和UserInfo.aspx?UserId=2的效果一樣,這裏就用到了URL地址重寫的目的。在其它動態語言如asp、JSP、PHP中要實現URL地址重寫需要藉助於其它的手段(有一些現成可用的用於實現URL地址重寫的dll庫),使用這些第三方手段時需要對IIS做些配置,如果網站放置在購買的虛擬主機上可能就不能夠使用了(這種情況我以前就遇見過,當時曾經做過一個J2EE的網站放在虛擬主機上要對Tomcat做配置才能啓用數據庫連接池,但是虛擬主機提供商不提供這種服務),其實在ASP.NET中可以通過自定義IHttpModule就可以實現URL地址重寫。
以下文字摘自《ASP.NET夜話》一書第十二章:
“從客戶端向IIS發出請求到客戶端得到請求的結果這個過程中有一些複雜的操作,涉及到一些非託管的類和非託管的方法,這裏就不提了。在託管環境中第一個處理這個請求的類是ISAPIRuntime類。
在ISAPIRuntime類中的ProcessRequest()方法中實例化HttpWorkerRequest類的實例,接着將HttpWorkerRequest的實例作爲參數交由HttpRuntime類處理,然後在HttpRuntime類中實例化HttpContext類的實例(這個HttpContext的實例中包含了當前HTTP請求的所有特定HTTP信息,爲實現了 IHttpModule 和 IHttpHandler 接口的類提供了對當前 HTTP 請求的 HttpContext 對象的引用,提供對請求的內部
Request、Response 和 Server 屬性的訪問),再將HttpContext的實例作爲參數交由HttpApplicationFactory類(此類是interal的,在外部程序集是不可見的,MSDN中也找不到此類的說明)用於實例化HttpApplication類的實例,在實例化HttpApplication類的實例的同時還會根據web.config中的配置(包括系統級和當前網站或虛擬目錄級)實例化所有實現IHttpModule接口的集合,這個集合是HttpModuleCollection類的實例,然後將HttpApplication類的實例作爲參數依次調用HttpModuleCollection中的每一個實現了IHttpModule接口的類的實例的Init()方法。
在這個Init()方法中開始處理一系列HttpApplication類的實例的事件。”
上面的文字中講到在實例化HttpApplication類時會根據web.config中的配置(包括系統級和當前網站或虛擬目錄級)實例化所有實現IHttpModule接口的集合,然後會將HttpApplication類的實例作爲參數依次調用每個實現了IHttpModule接口的類的實例的Init()方法,在Init方法中可以添加對請求的特殊處理。在HttpApplication中有很多事件,其中第一個事件就是BeginRequest事件,在這個事件中我們可以對用戶請求的URL進行判斷,如果滿足某種要求,可以按另外一種方式來進行處理。
比如,當接收到的用戶請求的URL是UserInfo(//d+).aspx這種形式時(這裏採用了正則表達式,表示的是UserInfo(數字).asp這種URL)我們將會運行UserInfo.aspx?UserId=(//d+)這樣一個URL,這樣網頁就能正常顯示了。
當然實現URL地址重寫還需要藉助一個類:HttpContext。HttpContext類中定義了RewritePath 方法,這個方法有四種重載形式,分別是:
RewritePath(String) 使用給定路徑重寫 URL。
RewritePath(String, Boolean) 使用給定路徑和一個布爾值重寫 URL,該布爾值用於指定是否修改服務器資源的虛擬路徑。
RewritePath(String, String, String) 使用給定路徑、路徑信息和一個布爾值重寫 URL,該布爾值用於指定是否修改服務器資源的虛擬路徑。
RewritePath(String, String, String, Boolean) 使用給定虛擬路徑、路徑信息、查詢字符串信息和一個布爾值重寫 URL,該布爾值用於指定是否將客戶端文件路徑設置爲重寫路徑。
對於這裏四個重載方法的區別我不一一詳細描述,因爲在這裏只用帶一個參數的重載方法就能滿足本文提出的要求。
我們的步驟如下:
首先編寫自定義IHttpModule實現,這個定義只定義了兩個方法Dispose()和Init()。在這裏我們可以不用關注Dispose()這個方法,這個方法是用來實現如何最終完成資源的釋放的。在Init方法中有一個HttpApplication參數,可以在方法中可以自定義對HttpApplication的事件處理方法。比如這裏我們的代碼如下:
- public void Init(HttpApplication context)
- {
- //context.BeginRequest是開始處理HTTP管線請求時發生的事件
- context.BeginRequest += new EventHandler(context_BeginRequest);
- //context.Error是當處理過程中發生異常時產生的事件
- //context.Error += new EventHandler(context_Error);
- }
然後在context_BeginRequest這個方法中自己編寫處理事件,我們編寫的代碼如下:
- void context_BeginRequest(object sender, EventArgs e)
- {
- HttpApplication application = (HttpApplication)sender;
- HttpContext context = application.Context;
- HttpResponse response = context.Response;
- string path = context.Request.Path;
- string file = System.IO.Path.GetFileName(path);
- //重寫後的URL地址
- Regex regex=new Regex("UserInfo(//d+).aspx",RegexOptions.Compiled);
- Match match=regex.Match(file);
- //如果滿足URL地址重寫的條件
- if(match.Success)
- {
- string userId = match.Groups[1].Value;
- string rewritePath = "UserInfo.aspx?UserId=" + userId;
- //將其按照UserInfo.aspx?UserId=123這樣的形式重寫,確保能正常執行
- context.RewritePath(rewritePath);
- }
- }
注意在上面的代碼中採用了正則表達式來進行匹配,使用正則表達式的好處就是在處理格式化文本時相當靈活。除此之外,我們在處理方法中僅僅對滿足要求的URL進行重寫,對於不滿足要求的URL則無需進行重寫,所以這樣就不會干擾沒有重寫的URL的正常運行(比如Index.aspx)。
從那段從《ASP.NET夜話》摘出的話中可以看出,僅僅是編寫自己的IHttpModule實現還是不夠的,我們還需要讓處理Web請求的程序直到我們編寫的IHttpModule實現的存在,這就需要在web.config中配置。在本實例中只需要在本ASP.NET項目中的web.config節點中增加一個<httpModules></httpModules>節點(如果已存在此節點則可以不用添加),然後在此節點中增加一個配置即可,對於本實例,這個節點最終內容如下:
- <httpModules>
- <add name="UrlReWriter" type="UrlReWriter"/>
- </httpModules>
這樣我們的URL地址重寫就成功了。
下面一個演示顯示用戶列表的鏈接的頁面設計代碼:
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head runat="server">
- <title>無標題頁</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <div>
- <ol>
- <li><a href="UserInfo1.aspx" mce_href="UserInfo1.aspx">編號爲1的用戶信息</a></li>
- <li><a href="UserInfo2.aspx" mce_href="UserInfo2.aspx">編號爲2的用戶信息</a></li>
- <li><a href="UserInfo3.aspx" mce_href="UserInfo3.aspx">編號爲3的用戶信息</a></li>
- <li><a href="UserInfo4.aspx" mce_href="UserInfo4.aspx">編號爲4的用戶信息</a></li>
- <li><a href="UserInfo.aspx?UserId=5" mce_href="UserInfo.aspx?UserId=5">編號爲5的用戶信息</a></li>
- </ol>
- </div>
- </form>
- </body>
- </html>
在UserInfo.aspx頁面的設計代碼如下:
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="UserInfo.aspx.cs" Inherits="UserInfo" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head runat="server">
- <title>無標題頁</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <div>
- <asp:Literal ID="Literal1" runat="server"></asp:Literal>
- <br />
- <img src="images/13.jpg" mce_src="images/13.jpg" width="283" height="485" title="MM" />
- </div>
- </form>
- </body>
- </html>
UserInfo.aspx頁面的對應邏輯代碼如下:
- using System;
- using System.Collections;
- using System.Configuration;
- using System.Data;
- using System.Web;
- using System.Web.Security;
- using System.Web.UI;
- using System.Web.UI.HtmlControls;
- using System.Web.UI.WebControls;
- using System.Web.UI.WebControls.WebParts;
- public partial class UserInfo : System.Web.UI.Page
- {
- protected void Page_Load(object sender, EventArgs e)
- {
- if (!Page.IsPostBack)
- {
- string queryString = Request.QueryString["UserId"];
- int userId=0;
- if (int.TryParse(queryString, out userId))
- {
- Literal1.Text = "這是編號爲" + userId.ToString() + "的用戶的信息。";
- }
- else
- {
- Literal1.Text = "錯誤的參數";
- }
- }
- }
- }
程序的演示效果:
點擊進行了URL重寫的鏈接的效果(即UserInfo1.aspx):
點擊沒有進行URL地址重寫的頁面的效果(即UserInfo.aspx?UserId=5):
可見儘管我們進行了URL地址重寫並不會頁面的最終顯示效果,因爲最終實際執行的還是UserInfo.aspx?UserId=數字這種形式的URL。
如果有興趣還可以對全站進行僞靜態化處理,所謂的僞靜態化就是所有的鏈接在瀏覽器地址欄裏看到的都是.html這樣的形式,實際上執行的仍是動態頁面,不過這就需要對IIS進行配置纔行。爲了簡單期間,本文中沒有使用數據庫,即使使用數據庫,只要重寫前的頁面能夠正常顯示,重寫之後是不會有什麼影響的。