2008年12月28日 星期日

ASP.NET 的 Url Rewrite

若希望當網友點下 http://www.xyz.com/123456789 可以自動連到 http://www.xyz.com/page.aspx?id=123456789 的話,就需要用到 Url Rewrite。

導到別頁的語法是:HttpContext 的 RewritePath() 或 Response 的 Redirect(),差別是後者是告知 client 端進行,前者是 server 端直接進行。

問題是 "時機",在哪裡可以寫程式來判斷現在的 url 的組成?

有時間的話,請看一下 MSDN 這篇 ASP.NET Application Life Cycle Overview for IIS 5.0 and 6.0:
http://msdn.microsoft.com/en-us/library/ms178473.aspx
HttpApplication 中依照不同程序會發生一堆事件。

我們可以在 Global.asax 中,當這些事件發生時,做 Url Rewrite 的工作。

另外一種做法,是寫一個實作 IHttpModule 的 HttpModule 類別,然後掛在 web.config 中,這樣每個 request 都會經過這個我們開發的 HttpModule,就可以進行 Url Rewrite 了。

首先,先開發這個 HttpModule,

UrlRewrite.cs:


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace idv.ben.httpmodule
{
public class UrlRewrite : IHttpModule
{
public UrlRewrite() { }

#region IHttpModule 成員

public void Dispose()
{
//throw new Exception("The method or operation is not implemented.");
}

public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(context_BeginRequest);
}

void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;

string queryString = context.Request.ServerVariables["QUERY_STRING"];
string currentURL = context.Request.Path.ToLower();
string pageParam = "";

System.Diagnostics.EventLog log = new System.Diagnostics.EventLog();
log.Source = "Application";
log.WriteEntry(string.Format("queryString={0}, currentURL={1}"
, queryString
, currentURL
));

if (currentURL.EndsWith(".aspx"))
{
int idx = currentURL.LastIndexOf("/");
string page = currentURL.Substring(idx + 1);
currentURL = currentURL.Substring(0, idx);

bool fileExists = false;
while (idx != -1)
{
string paramName = "";
string paramValue = "";

idx = currentURL.LastIndexOf("/");
if (idx != -1)
{
paramValue = currentURL.Substring(idx + 1);
currentURL = currentURL.Substring(0, idx);
}

idx = currentURL.LastIndexOf("/");
if (idx != -1)
{
paramName = currentURL.Substring(idx + 1);
currentURL = currentURL.Substring(0, idx);
}

if (!paramName.Equals("") && !paramValue.Equals(""))
{
if (!pageParam.Equals(""))
pageParam += "&";
pageParam += string.Format("{0}={1}", paramName, paramValue);
}

try
{
if (System.IO.File.Exists(context.Server.MapPath(currentURL + "/" + page)))
{
fileExists = true;
break;
}
}
catch { break; }
}

if (fileExists)
{
if (queryString != string.Empty)
context.RewritePath(currentURL + "/" + page + "?" + pageParam + "&" + queryString, false);
else
context.RewritePath(currentURL + "/" + page + "?" + pageParam, false);
}
else
{
context.Response.Write("page not found!!");
context.Response.End();
}
}
}

#endregion
}
}


只要注意在 init() 時,我們設定 HttpApplication 的 BeginRequest 事件發生時,要處理的動作。

至於在 context_BeginRequest(),該如何將 網友瀏覽的 Url 轉成你期望的 Url,這就視需求而訂。我這裡的用途是:

* url 的最後一定要是一個 *.aspx
* 去除 page (*.aspx) 之外,將 url 由後往前組合 變數值對(name=value),每找到一組可成為 變數值對 後,原本的 url 就縮減去除那組 變數值對。
* 判斷 url(某層資料夾) 下是否存在有 page (*.aspx)。如果找到 page 所在的 url 層級,就可以進行 Url Rewrite 了。
* 所有找出來的 變數值對 與 原始url 的 query string,都會成為新的 query string。

以上,是我的範例的做法,你們可以寫自己的一套。




接下來,就可以將這個 HttpModule 掛到 web.config 開始服務了,請於 web.config 中加入以下這段:


<system.web>
<httpModules>
<add name="UrlRewrite" type="idv.ben.httpmodule.UrlRewrite"/>
</httpModules>
</system.web>


最後,準備一個 view.aspx 做測試`:
<%@ Page Language="C#" AutoEventWireup="true" %>

<script runat="server">

void Page_Load(object sender, EventArgs e)
{
Response.Write("qs = " + Request.ServerVariables["QUERY_STRING"]);
}

</script>

<!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>aa</title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>



瀏覽效果如下:

一般:http://localhost:13833/WebSite1/var1/123/var2/456/view.aspx



加額外的參數:http://localhost:13833/WebSite1/var1/123/var2/456/view.aspx?var3=789&var4=999



變數值對有誤、找不到網頁:http://localhost:13833/WebSite1/xxx/var1/123/var2/456/view.aspx?var3=789&var4=999

2 則留言:

Sean Lin 提到...

同樣的功能用 ISAPI Extension 寫一個 IIS filter 效率更好!
比 Global.asax 還早發生唷!

用 PHTTP_FILTER_PREPROC_HEADERS 的 GetHeader 方法找 url 取得原網址。
再用 SetHeader 設定 url 改成自己想要的 。
結果一樣是在 server 端神不知鬼不覺的執行另一隻 asp(x) 程式

匿名 提到...

請問一下, 如果你的view.aspx要放到資料夾中,例如:pages/view.aspx 好像會失效呢