|
這篇文章描述了一個(gè)完整的 ASP.NET 2.0 URL 重寫方案。這個(gè)方案使用正則表達(dá)式來(lái)定義重寫規(guī)則并解決通過(guò)虛擬 URLs 訪問(wèn)頁(yè)面產(chǎn)生回發(fā)事件的一些可能的困難。
為什么要重寫 URL ?
將 URL 重寫方法應(yīng)用到你的 ASP.NET 應(yīng)用程序的兩個(gè)主要原因是:可用性和可維護(hù)性。
可用性
誰(shuí)都知道,相對(duì)于難于辨認(rèn)的帶參數(shù)的長(zhǎng)的查詢路徑,用戶更喜歡一些短的、簡(jiǎn)潔的 URL。任何時(shí)候,一個(gè)容易記住和敲入的路徑比添加到收藏夾更有用。其次,當(dāng)一個(gè)瀏覽器的收藏夾不可用時(shí),記住的地址總比在搜索引擎中輸入關(guān)鍵字進(jìn)行搜索,然后再查找要強(qiáng)的多。比較下面的兩個(gè)地址:
(1) | http://www.somebloghost.com/Blogs/Posts.ASPx?Year=2006&Month=12&Day=10 |
(2) | http://www. somebloghost.com/Blogs/2006/12/10/ |
第一個(gè) URL 包含了查詢字符串;第二個(gè)URL包含的信息可以讓用戶清楚的看到他看的東西,它還可以使用戶更容易的修改地址欄的內(nèi)容,如:http://www.somehost.com/Blogs/2006/12/.
可維護(hù)性
在很多WEB應(yīng)用程序中,開(kāi)發(fā)人員經(jīng)常會(huì)將頁(yè)面從一個(gè)目錄移到另一個(gè)目錄,讓我們假設(shè)一開(kāi)始有兩個(gè)可用頁(yè)面: http://www.somebloghost.com/Info/Copyright.ASPx 和 http://www.somebloghost.com/Support/Contacts.ASPx,但是后來(lái)開(kāi)發(fā)者將 Copyright.ASPx 和 Contacts.ASPx 移到了 Help 目錄,用戶收藏起來(lái)地址就需要重新定位。這個(gè)問(wèn)題雖然可以簡(jiǎn)單的用 Response.Redirect(new location) 來(lái)解決,但是如果有成百上千的頁(yè)面呢?應(yīng)用程序中就會(huì)包含大量的無(wú)效鏈接。
使用 URL 重寫,允許用戶只需修改配置文件,這種方法可以讓開(kāi)發(fā)者將web應(yīng)用程序邏輯結(jié)構(gòu)與物理結(jié)構(gòu)獨(dú)立開(kāi)來(lái)。
ASP.NET 2.0 中的原有的URL 映射
ASP.NET 2.0 為 web 應(yīng)用程序提供了一個(gè)開(kāi)箱即用的映射靜態(tài) URL 的解決方案。這個(gè)方案不用編寫代碼就可以在 web.config 中將舊的 URLs 映射到新的地址。 要使用 URL 映射,只需在 web.config 文件的 system.web 節(jié)中創(chuàng)建一個(gè)新的 urlMappings 節(jié) ,并添加要映射的地址 (“ ~/ ”指向應(yīng)用程序的根目錄):
<urlMappings enabled="true">
<add url="~/Info/Copyright.ASPx" mappedUrl="~/Help/Copyright.ASPx" />
<add url="~/Support/Contacts.ASPx" mappedUrl="~/Help/Contacts.ASPx" />
</urlMappings>
這樣,如果用戶輸入 http://www.somebloghost.com/Support/Contacts.ASPx, 它將看到 http://www.somebloghost.com/Help/Contacts.ASPx , 而他并不知道那個(gè)頁(yè)已經(jīng)移除。
這個(gè)方案對(duì)于只有兩個(gè)頁(yè)面被移到其它位置的情況是足夠的。但它對(duì)有一打的需要重定位的頁(yè)或者需要?jiǎng)?chuàng)建一個(gè)整潔的URL來(lái)說(shuō),它是不合適的。另一個(gè)使用ASP.NET 的原有的URL映射技術(shù)的不太好的地方是:如果 Contacts.ASPx 頁(yè)包含的元素在回發(fā)到服務(wù)器時(shí)(這是非常可能的), 用戶將會(huì)驚奇的發(fā)現(xiàn)地址 http://www.somebloghost.com/Support/Contacts.ASPx 卻變成了 http://www.somebloghost.com/Help/Contacts.ASPx
。 這是因?yàn)?a href=/itjie/ASPjishu/ target=_blank class=infotextkey>ASP.NET 引擎用頁(yè)面的實(shí)際地址修改了表單form 的 action 屬性 ,所以表單就變成了下面的樣子:
<form name="formTest" method="post"
action="http://www.simple-talk.com/Help/Contacts.ASPx" id="formTest">
</form>
這樣看來(lái),URL 映射在ASP.NET 2.0 中幾乎是無(wú)用的。我們應(yīng)當(dāng)能夠使用一個(gè)映射規(guī)則來(lái)指定一系列相似的 URL。最好的解決方案就是使用正則表達(dá)式 ( Wikipedia 上可以查看概覽,and 在 .NET 下的實(shí)現(xiàn)可以查看 MSDN), 但由于 ASP.NET 2.0 映射不支持正則表達(dá)式,所以我們需要開(kāi)發(fā)一個(gè)內(nèi)建到 URL 映射的不同的方案- URL 重寫模塊。 最好的方法就是創(chuàng)建一個(gè)可重用的、簡(jiǎn)單的配置模塊來(lái)實(shí)現(xiàn),顯然我們應(yīng)創(chuàng)建一個(gè) HTTP 模塊 (關(guān)于 HTTP 模塊的詳細(xì)信息請(qǐng)查看 MSDN 雜志) 并在獨(dú)立的程序集中實(shí)現(xiàn)。要使這個(gè)程序集簡(jiǎn)單易用,我們應(yīng)實(shí)現(xiàn)這個(gè)重寫引擎的可配置性,即能夠在 web.config 中指定規(guī)則。
在開(kāi)發(fā)過(guò)程中,我們應(yīng)能使這個(gè)重寫模塊打開(kāi)或關(guān)閉 (比如你有一個(gè)較難捕獲的bug,而它可能是由不正確的重寫模塊引起的)這樣在 web.config 中對(duì)重寫模塊的配置節(jié)進(jìn)行打開(kāi)或關(guān)閉就成為一個(gè)選擇。這樣,在 web.config 中,一個(gè)配置節(jié)的示例如下:
<rewriteModule>
<rewriteOn>true</rewriteOn>
<rewriteRules>
<rule source="(/d+)/(/d+)/(/d+)/"
destination="Posts.ASPx?Year=$1&Month=$2&Day=$3"/><rule source="(.*)/Default.ASPx"
destination="Default.ASPx?Folder=$1"/></rewriteRules>
</rewriteModule>
這樣,所有像: http://localhost/Web/2006/12/10/ 這樣的請(qǐng)示,將會(huì)在內(nèi)部將會(huì)用帶參數(shù)的請(qǐng)求重定向到 Posts.ASPx 。
請(qǐng)注意: web.config 是一個(gè)結(jié)構(gòu)良好的 XML 文件, 它禁止在屬性值中使用 & 符號(hào),所以在例子中,應(yīng)當(dāng)使用 & 代替。
要在配置文件中使用這個(gè)重寫模塊,還需要注冊(cè)節(jié)和指定處理模塊,像下面這樣增加一個(gè)configSections配置節(jié):
<configSections><sectionGroup name="modulesSection">
<section name="rewriteModule" type="RewriteModule.
RewriteModuleSectionHandler, RewriteModule"/></sectionGroup>
</configSections>
這樣你就可以在 configSections 節(jié)的后面這樣使用了:
<modulesSection>
<rewriteModule>
<rewriteOn>true</rewriteOn>
<rewriteRules>
<rule source="(/d+)/(/d+)/(/d+)/" destination="Post.ASPx?Year=$1&Month=$2&Day=$3"/>
<rule source="(.*)/Default.ASPx" destination="Default.ASPx?Folder=$1"/>
</rewriteRules>
</rewriteModule>
</modulesSection>
另一個(gè)我們?cè)陂_(kāi)發(fā)重寫模塊過(guò)程中要做的就是還需要允許在虛擬路徑中傳遞參數(shù),象這樣: http://www.somebloghost.com/2006/12/10/?Sort=Desc&SortBy=Date 。所以我們還需要有一個(gè)檢測(cè)通過(guò)虛擬 URL 傳遞參數(shù)的解決方案。
接下來(lái)讓我們來(lái)創(chuàng)建類庫(kù)。首先,我們要引用 System.Web 程序集,這樣我們可以實(shí)現(xiàn)一些基于 web 特殊功能。如果要使我們的模塊能夠訪問(wèn) web.config,還需要引用 System.Configuration 程序集。
處理配置節(jié)
要能處理 web.config 中的配置,我們必需創(chuàng)建一個(gè)實(shí)現(xiàn)了 IConfigurationSectionHandler 接口的類 (詳情查看 MSDN )。如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Web;
using System.Xml;
namespace RewriteModule
{
public class RewriteModuleSectionHandler : IConfigurationSectionHandler
{
private XmlNode _XmlSection;
private string _RewriteBase;
private bool _RewriteOn;
public XmlNode XmlSection
{
get { return _XmlSection; }
}
public string RewriteBase
{
get { return _RewriteBase; }
}
public bool RewriteOn
{
get { return _RewriteOn; }
}
public object Create(object parent,
object configContext,
System.Xml.XmlNode section){
// set base path for rewriting module to
// application root
_RewriteBase = HttpContext.Current.Request.ApplicationPath + "/";
// process configuration section
// from web.config
try
{
_XmlSection = section;
_RewriteOn = Convert.ToBoolean(
section.SelectSingleNode("rewriteOn").InnerText);}
catch (Exception ex)
{
throw (new Exception("Error while processing RewriteModule
configuration section.", ex));}
return this;
}
}
}
RewriteModuleSectionHandler 類將在 web.config 中的 XmlNode 通過(guò)調(diào)用 Create 方法初始化。XmlNode 類的 SelectSingleNode 方法被用來(lái)返回模塊的配置值。
使用重寫的 URL 的參數(shù)
在處理象 http://www. somebloghost.com/Blogs/gaidar/?Sort=Asc (這是一個(gè)帶參數(shù)的虛擬 URL ) 虛擬的 URLS 時(shí),能夠清楚的辨別通過(guò)虛擬路徑傳遞的參數(shù)是非常重要的,如下:
<rule source="(.*)/Default.ASPx" destination="Default.ASPx?Folder=$1"/>,
你可能使用這樣的 URL:
http://www. somebloghost.com/gaidar/?Folder=Blogs
它的效果和下面的相似:
http://www. somebloghost.com/Blogs/gaidar/
要處理這個(gè)問(wèn)題,我們需要對(duì)'虛擬路徑參數(shù)' 進(jìn)行包裝。這可以是通過(guò)一個(gè)靜態(tài)的方法去訪問(wèn)當(dāng)前的參數(shù)集:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Web;
namespace RewriteModule
{
public class RewriteContext
{
// returns actual RewriteContext instance for
// current request
public static RewriteContext Current
{
get
{
// Look for RewriteContext instance in
// current HttpContext. If there is no RewriteContextInfo
// item then this means that rewrite module is turned off
if(HttpContext.Current.Items.Contains("RewriteContextInfo"))
return (RewriteContext)
HttpContext.Current.Items["RewriteContextInfo"];else
return new RewriteContext();
}
}
public RewriteContext()
{
_Params = new NameValueCollection();
_InitialUrl = String.Empty;
}
public RewriteContext(NameValueCollection param, string url)
{
_InitialUrl = url;
_Params = new NameValueCollection(param);
}
private NameValueCollection _Params;
public NameValueCollection Params
{
get { return _Params; }
set { _Params = value; }
}
private string _InitialUrl;
public string InitialUrl
{
get { return _InitialUrl; }
set { _InitialUrl = value; }
}
}
}
可以看到,這樣就可以通過(guò)RewriteContext.Current 集合來(lái)訪問(wèn) “虛擬路徑參數(shù)”了,所有的參數(shù)都被指定成了虛擬路徑或頁(yè)面,而不是像查詢字符串那樣了。
重寫 URL
接下來(lái),讓我們嘗試重寫。首先,我們要讀取配置文件中的重寫規(guī)則。其次,我們要檢查那些在 URL 中與規(guī)則不符的部分,如果有,進(jìn)行重寫并以適當(dāng)?shù)捻?yè)執(zhí)行。
創(chuàng)建一個(gè) HttpModule:
class RewriteModule : IHttpModule
{public void Dispose() { }
public void Init(HttpApplication context){}
}
當(dāng)我們添加 RewriteModule_BeginRequest 方法以處理不符合規(guī)則的 URL時(shí),我們要檢查給定的 URL 是否包含參數(shù),然后調(diào)用 HttpContext.Current.RewritePath 來(lái)進(jìn)行控制并給出合適的 ASP.NET 頁(yè)。
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Configuration;
using System.Xml;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.IO;
using System.Collections.Specialized;
namespace RewriteModule
{
class RewriteModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
// it is necessary to
context.BeginRequest += new EventHandler(
RewriteModule_BeginRequest);}
void RewriteModule_BeginRequest(object sender, EventArgs e)
{
RewriteModuleSectionHandler cfg =
(RewriteModuleSectionHandler)
ConfigurationManager.GetSection
("modulesSection/rewriteModule");
// module is turned off in web.config
if (!cfg.RewriteOn) return;
string path = HttpContext.Current.Request.Path;
// there us nothing to process
if (path.Length == 0) return;
// load rewriting rules from web.config
// and loop through rules collection until first match
XmlNode rules = cfg.XmlSection.SelectSingleNode("rewriteRules");
foreach (XmlNode xml in rules.SelectNodes("rule"))
{
try
{
Regex re = new Regex(
cfg.RewriteBase + xml.Attributes["source"].InnerText,
RegexOptions.IgnoreCase);Match match = re.Match(path);
if (match.Success)
{
path = re.Replace(
path,
xml.Attributes["destination"].InnerText);if (path.Length != 0)
{
// check for QueryString parameters
if(HttpContext.Current.Request.QueryString.Count != 0)
{
// if there are Query String papameters
// then append them to current path
string sign = (path.IndexOf('?') == -1) ? "?" : "&";
path = path + sign +
HttpContext.Current.Request.QueryString.ToString();}
// new path to rewrite to
string rew = cfg.RewriteBase + path;
// save original path to HttpContext for further use
HttpContext.Current.Items.Add(
"OriginalUrl",
HttpContext.Current.Request.RawUrl);
// rewrite
HttpContext.Current.RewritePath(rew);
}
return;
}
}
catch (Exception ex)
{
throw (new Exception("Incorrect rule.", ex));
}
}
return;
}
}
}
這個(gè)方法必須注冊(cè):
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);
}
但這些僅僅完成了一半,因?yàn)橹貙懩K還要處理表單的回發(fā)和虛擬路徑參數(shù)集合,而這段代碼中你會(huì)發(fā)現(xiàn)并沒(méi)處理這些。讓我們先把虛擬路徑參數(shù)放到一邊,先來(lái)正確地處理最主要的回發(fā)。
如果我們運(yùn)行上面的代碼,并通過(guò)查看 ASP.NET 的 HTML 源代碼 的 action 會(huì)發(fā)現(xiàn),它竟然包含了一個(gè) ASP.NET 的實(shí)際路徑頁(yè)。例如,我們使用頁(yè) ~/Posts.ASPx 來(lái)處理像 http://www. somebloghost.com/Blogs/2006/12/10/Default.ASPx 的請(qǐng)求, 發(fā)現(xiàn) action="/Posts.ASPx"。這意味著用戶并沒(méi)有使用虛擬路徑進(jìn)行回發(fā),而是使用了實(shí)際的 http://www. somebloghost.com/Blog.ASPx. 這個(gè)并不是我們需要的。所以,需要加一段代碼來(lái)處理這些不希望的結(jié)果。
首先,我們要在 HttpModule 注冊(cè)和實(shí)現(xiàn)一個(gè)另外的方法:
public void Init(HttpApplication context)
{
// it is necessary to
context.BeginRequest += new EventHandler(
RewriteModule_BeginRequest);context.PreRequestHandlerExecute += new EventHandler(
RewriteModule_PreRequestHandlerExecute);}
void RewriteModule_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if ((app.Context.CurrentHandler is Page) &&
app.Context.CurrentHandler != null){
Page pg = (Page)app.Context.CurrentHandler;
pg.PreInit += new EventHandler(Page_PreInit);
}
}
這個(gè)方法檢查用戶是否請(qǐng)求了一個(gè)正常的 ASP.NET 頁(yè),然后為該頁(yè)的 PreInit 事件增加處理過(guò)程。這兒 RewriteContext 將處理實(shí)際參數(shù),然后二次重寫URL。二次重寫是必需的,以使 ASP.NET 能夠在它的表單的action屬性中使用一個(gè)虛擬路徑。
void Page_PreInit(object sender, EventArgs e)
{
// restore internal path to original
// this is required to handle postbacks
if (HttpContext.Current.Items.Contains("OriginalUrl"))
{
string path = (string)HttpContext.Current.Items["OriginalUrl"];
// save query string parameters to context
RewriteContext con = new RewriteContext(
HttpContext.Current.Request.QueryString, path);
HttpContext.Current.Items["RewriteContextInfo"] = con;
if (path.IndexOf("?") == -1)
path += "?";
HttpContext.Current.RewritePath(path);
}
}
最后,我們來(lái)看一下在我們的重寫模塊程序集中的三個(gè)類:
在 web.config 中注冊(cè)重寫模塊
要使用重寫模塊,需要在配置文件中的 httpModules 節(jié)注冊(cè)重寫模塊,如下:
<httpModules>
<add name="RewriteModule" type="RewriteModule.RewriteModule, RewriteModule"/>
</httpModules>
使用重寫模塊
在使用重寫模塊時(shí),需要注意:
- 在 web.config 中來(lái)使用一些特殊字符是不可能的,因?yàn)樗且粋€(gè)結(jié)構(gòu)良好的 XML 文件,因此,你只能用 HTML 編碼的字符代替,如:使用 & 代替 &。
- 要在你的 ASPX 中使用相對(duì)路徑,需要在HTML標(biāo)簽調(diào)用 ResolveUrl 方法,如: <img src="<%=ResolveUrl("~/Images/Test.jpg")%>" />。
- Bear in mind the greediness of regular expressions and put rewriting rules to web.config in order of their greediness, for instance:
<rule source="Directory/(.*)/(.*)/(.*)/(.*).ASPx"
destination="Directory/Item.ASPx?
Source=$1&Year=$2&ValidTill=$3&Sales=$4"/>
<rule source="Directory/(.*)/(.*)/(.*).ASPx"
destination="Directory/Items.ASPx?
Source=$1&Year=$2&ValidTill=$3"/>
<rule source="Directory/(.*)/(.*).ASPx"
destination="Directory/SourceYear.ASPx?
Source=$1&Year=$2&"/>
<rule source="Directory/(.*).ASPx"
destination="Directory/Source.ASPx?Source=$1"/>
- 如果你要在頁(yè)面中使用 RewriteModule 而不使用 .ASPx,就必須在 IIS 中進(jìn)行配置以使用期望的擴(kuò)展映射到請(qǐng)求頁(yè),如下節(jié)所述:
IIS 配置: 使用帶擴(kuò)展的重寫模塊代替 .ASPx
要使用帶擴(kuò)展的重寫模塊代替 .ASPx (如 .html or .xml), 必須配置 IIS ,以使這些擴(kuò)展映射到 ASP.NET 引擎 (ASP.NET ISAPI 擴(kuò)展)。要進(jìn)行這些設(shè)置,需要以管理員身份登錄。
打開(kāi) IIS 管理控制臺(tái),并選擇你要配置的站點(diǎn)的虛擬路徑:
Windows XP (IIS 5)
Virtual Directory "RW"
Windows 2003 Server (IIS 6)
Default Web Site
然后在虛擬路徑標(biāo)簽上點(diǎn)擊 Configuration… 按鈕 (或如果要使用整個(gè)站點(diǎn)都做映射就選擇主目錄標(biāo)簽)。
Windows XP (IIS 5)
Windows 2003 Server (IIS 6)
接下來(lái),點(diǎn)擊添加按鈕,并輸入一個(gè)擴(kuò)展,你還需要指定一個(gè) ASP.NET ISAPI 擴(kuò)展,注意去掉選項(xiàng)的對(duì)勾以檢查文件是否存在。
如果你要把所有的擴(kuò)展都映射到 ASP.NET,對(duì)Windows XP上的 IIS 5 來(lái)說(shuō)只需要設(shè)置 .* 到 ASP.NET ISAPI ,但對(duì) IIS 6 就不一樣了,點(diǎn)擊“添加”然后指定 ASP.NET ISAPI 擴(kuò)展。
總結(jié)
現(xiàn)在,我們已經(jīng)創(chuàng)建了一個(gè)簡(jiǎn)單的但非常強(qiáng)大的 ASP.NET 重寫模塊,它支持可基于正則表達(dá)式的 URLs 和頁(yè)面回發(fā),這個(gè)解決方案是容易實(shí)現(xiàn)的,并且提供給用戶的例子也是可用的,它可以用簡(jiǎn)短的、整潔的URL來(lái)替代查詢字符串參數(shù)。 要使用這個(gè)模塊,只需簡(jiǎn)單在你的應(yīng)用程序中對(duì) RewriteModule 進(jìn)行引用,然后在 web.config 文件中添加幾行代碼以使你不想顯示的 URL 通過(guò)正則表達(dá)式代替。這個(gè)重寫模塊是很容易部署的,因?yàn)橹恍枰趙eb.config中修改任何“虛擬”的URL即可,如果你需要進(jìn)行測(cè)試,還可以對(duì)重寫模塊進(jìn)行關(guān)閉。
要想對(duì)重寫模塊有一個(gè)深入的了解,你可以查看本文提供的原代碼。我相信你會(huì)發(fā)現(xiàn)這是一個(gè)比ASP.NET提供的原始映射更好的體驗(yàn)。
AspNet技術(shù):一個(gè)完整的ASP.NET 2.0 URL重寫方案[翻譯],轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。