這篇文章描述了一個完整的 ASP.NET 2.0 URL 重寫方案。這個方案使用正則表達式來定義重寫規(guī)則并解決通過虛擬 URLs 訪問頁面產(chǎn)生回發(fā)事件的一些可能的困難。
將 URL 重寫方法應(yīng)用到你的 ASP.Net 應(yīng)用程序的兩個主要原因是:可用性和可維護性。
誰都知道,相對于難于辨認的帶參數(shù)的長的查詢路徑,用戶更喜歡一些短的、簡潔的 URL。任何時候,一個容易記住和敲入的路徑比添加到收藏夾更有用。其次,當一個瀏覽器的收藏夾不可用時,記住的地址總比在搜索引擎中輸入關(guān)鍵字進行搜索,然后再查找要強的多。比較下面的兩個地址:
(1) |
http://www.somebloghost.com/Blogs/Posts.aspx?Year=2006&Month=12&Day=10 |
(2) |
http://www. somebloghost.com/Blogs/2006/12/10/ |
第一個 URL 包含了查詢字符串;第二個URL包含的信息可以讓用戶清楚的看到他看的東西,它還可以使用戶更容易的修改地址欄的內(nèi)容,如:http://www.somehost.com/Blogs/2006/12/.
在很多WEB應(yīng)用程序中,開發(fā)人員經(jīng)常會將頁面從一個目錄移到另一個目錄,讓我們假設(shè)一開始有兩個可用頁面: http://www.somebloghost.com/Info/Copyright.aspx 和 http://www.somebloghost.com/Support/Contacts.aspx,但是后來開發(fā)者將 Copyright.aspx 和 Contacts.aspx 移到了 Help 目錄,用戶收藏起來地址就需要重新定位。這個問題雖然可以簡單的用 Response.Redirect(new location) 來解決,但是如果有成百上千的頁面呢?應(yīng)用程序中就會包含大量的無效鏈接。
使用 URL 重寫,允許用戶只需修改配置文件,這種方法可以讓開發(fā)者將web應(yīng)用程序邏輯結(jié)構(gòu)與物理結(jié)構(gòu)獨立開來。
ASP.NET 2.0 為 web 應(yīng)用程序提供了一個開箱即用的映射靜態(tài) URL 的解決方案。這個方案不用編寫代碼就可以在 web.config 中將舊的 URLs 映射到新的地址。 要使用 URL 映射,只需在 web.config 文件的 system.web 節(jié)中創(chuàng)建一個新的 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 , 而他并不知道那個頁已經(jīng)移除。
這個方案對于只有兩個頁面被移到其它位置的情況是足夠的。但它對有一打的需要重定位的頁或者需要創(chuàng)建一個整潔的URL來說,它是不合適的。另一個使用Asp.Net 的原有的URL映射技術(shù)的不太好的地方是:如果 Contacts.aspx 頁包含的元素在回發(fā)到服務(wù)器時(這是非??赡艿?, 用戶將會驚奇的發(fā)現(xiàn)地址 http://www.somebloghost.com/Support/Contacts.aspx 卻變成了 http://www.somebloghost.com/Help/Contacts.aspx
。 這是因為ASP.NET 引擎用頁面的實際地址修改了表單form 的 action 屬性 ,所以表單就變成了下面的樣子:
<form name="formTest" method="post"
action="http://www.simple-talk.com/Help/Contacts.aspx" id="formTest">
</form>
這樣看來,URL 映射在ASP.NET 2.0 中幾乎是無用的。我們應(yīng)當能夠使用一個映射規(guī)則來指定一系列相似的 URL。最好的解決方案就是使用正則表達式 ( Wikipedia 上可以查看概覽,and 在 .NET 下的實現(xiàn)可以查看 MSDN), 但由于 ASP.NET 2.0 映射不支持正則表達式,所以我們需要開發(fā)一個內(nèi)建到 URL 映射的不同的方案- URL 重寫模塊。 最好的方法就是創(chuàng)建一個可重用的、簡單的配置模塊來實現(xiàn),顯然我們應(yīng)創(chuàng)建一個 HTTP 模塊 (關(guān)于 HTTP 模塊的詳細信息請查看 MSDN 雜志) 并在獨立的程序集中實現(xiàn)。要使這個程序集簡單易用,我們應(yīng)實現(xiàn)這個重寫引擎的可配置性,即能夠在 web.config 中指定規(guī)則。
在開發(fā)過程中,我們應(yīng)能使這個重寫模塊打開或關(guān)閉 (比如你有一個較難捕獲的bug,而它可能是由不正確的重寫模塊引起的)這樣在 web.config 中對重寫模塊的配置節(jié)進行打開或關(guān)閉就成為一個選擇。這樣,在 web.config 中,一個配置節(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/ 這樣的請示,將會在內(nèi)部將會用帶參數(shù)的請求重定向到 Posts.aspx 。
請注意: web.config 是一個結(jié)構(gòu)良好的 XML 文件, 它禁止在屬性值中使用 & 符號,所以在例子中,應(yīng)當使用 & 代替。
要在配置文件中使用這個重寫模塊,還需要注冊節(jié)和指定處理模塊,像下面這樣增加一個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>
另一個我們在開發(fā)重寫模塊過程中要做的就是還需要允許在虛擬路徑中傳遞參數(shù),象這樣: http://www.somebloghost.com/2006/12/10/?Sort=Desc&SortBy=Date 。所以我們還需要有一個檢測通過虛擬 URL 傳遞參數(shù)的解決方案。
接下來讓我們來創(chuàng)建類庫。首先,我們要引用 System.Web 程序集,這樣我們可以實現(xiàn)一些基于 web 特殊功能。如果要使我們的模塊能夠訪問 web.config,還需要引用 System.Configuration 程序集。
要能處理 web.config 中的配置,我們必需創(chuàng)建一個實現(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 通過調(diào)用 Create 方法初始化。XmlNode 類的 SelectSingleNode 方法被用來返回模塊的配置值。
在處理象 http://www. somebloghost.com/Blogs/gaidar/?Sort=Asc (這是一個帶參數(shù)的虛擬 URL ) 虛擬的 URLS 時,能夠清楚的辨別通過虛擬路徑傳遞的參數(shù)是非常重要的,如下:
<rule source="(.*)/Default.aspx" destination="Default.aspx?Folder=$1"/>,
你可能使用這樣的 URL:
http://www. somebloghost.com/gaidar/?Folder=Blogs
它的效果和下面的相似:
http://www. somebloghost.com/Blogs/gaidar/
要處理這個問題,我們需要對'虛擬路徑參數(shù)' 進行包裝。這可以是通過一個靜態(tài)的方法去訪問當前的參數(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; }
}
}
}
可以看到,這樣就可以通過RewriteContext.Current 集合來訪問 “虛擬路徑參數(shù)”了,所有的參數(shù)都被指定成了虛擬路徑或頁面,而不是像查詢字符串那樣了。
接下來,讓我們嘗試重寫。首先,我們要讀取配置文件中的重寫規(guī)則。其次,我們要檢查那些在 URL 中與規(guī)則不符的部分,如果有,進行重寫并以適當?shù)捻搱?zhí)行。
創(chuàng)建一個 HttpModule:
class RewriteModule : IHttpModule
{public void Dispose() { }
public void Init(HttpApplication context){}
}
當我們添加 RewriteModule_BeginRequest 方法以處理不符合規(guī)則的 URL時,我們要檢查給定的 URL 是否包含參數(shù),然后調(diào)用 HttpContext.Current.RewritePath 來進行控制并給出合適的 ASP.NET 頁。
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;
}
}
}
這個方法必須注冊:
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);
}
但這些僅僅完成了一半,因為重寫模塊還要處理表單的回發(fā)和虛擬路徑參數(shù)集合,而這段代碼中你會發(fā)現(xiàn)并沒處理這些。讓我們先把虛擬路徑參數(shù)放到一邊,先來正確地處理最主要的回發(fā)。
如果我們運行上面的代碼,并通過查看 ASP.NET 的 HTML 源代碼 的 action 會發(fā)現(xiàn),它竟然包含了一個 ASP.NET 的實際路徑頁。例如,我們使用頁 ~/Posts.aspx 來處理像 http://www. somebloghost.com/Blogs/2006/12/10/Default.aspx 的請求, 發(fā)現(xiàn) action="/Posts.aspx"。這意味著用戶并沒有使用虛擬路徑進行回發(fā),而是使用了實際的 http://www. somebloghost.com/Blog.aspx. 這個并不是我們需要的。所以,需要加一段代碼來處理這些不希望的結(jié)果。
首先,我們要在 HttpModule 注冊和實現(xiàn)一個另外的方法:
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);
}
}
這個方法檢查用戶是否請求了一個正常的 ASP.NET 頁,然后為該頁的 PreInit 事件增加處理過程。這兒 RewriteContext 將處理實際參數(shù),然后二次重寫URL。二次重寫是必需的,以使 ASP.NET 能夠在它的表單的action屬性中使用一個虛擬路徑。
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);
}
}
最后,我們來看一下在我們的重寫模塊程序集中的三個類:
要使用重寫模塊,需要在配置文件中的 httpModules 節(jié)注冊重寫模塊,如下:
<httpModules>
<add name="RewriteModule" type="RewriteModule.RewriteModule, RewriteModule"/>
</httpModules>
在使用重寫模塊時,需要注意:
<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"/>
要使用帶擴展的重寫模塊代替 .aspx (如 .html or .xml), 必須配置 IIS ,以使這些擴展映射到 ASP.NET 引擎 (ASP.NET ISAPI 擴展)。要進行這些設(shè)置,需要以管理員身份登錄。
打開 IIS 管理控制臺,并選擇你要配置的站點的虛擬路徑:
Windows XP (IIS 5)
Virtual Directory "RW"
Windows 2003 Server (IIS 6)
Default Web Site
然后在虛擬路徑標簽上點擊 Configuration… 按鈕 (或如果要使用整個站點都做映射就選擇主目錄標簽)。
Windows XP (IIS 5)
Windows 2003 Server (IIS 6)
接下來,點擊添加按鈕,并輸入一個擴展,你還需要指定一個 ASP.NET ISAPI 擴展,注意去掉選項的對勾以檢查文件是否存在。
如果你要把所有的擴展都映射到 ASP.NET,對Windows XP上的 IIS 5 來說只需要設(shè)置 .* 到 ASP.NET ISAPI ,但對 IIS 6 就不一樣了,點擊“添加”然后指定 ASP.NET ISAPI 擴展。
現(xiàn)在,我們已經(jīng)創(chuàng)建了一個簡單的但非常強大的 ASP.NET 重寫模塊,它支持可基于正則表達式的 URLs 和頁面回發(fā),這個解決方案是容易實現(xiàn)的,并且提供給用戶的例子也是可用的,它可以用簡短的、整潔的URL來替代查詢字符串參數(shù)。 要使用這個模塊,只需簡單在你的應(yīng)用程序中對 RewriteModule 進行引用,然后在 web.config 文件中添加幾行代碼以使你不想顯示的 URL 通過正則表達式代替。這個重寫模塊是很容易部署的,因為只需要在web.config中修改任何“虛擬”的URL即可,如果你需要進行測試,還可以對重寫模塊進行關(guān)閉。
要想對重寫模塊有一個深入的了解,你可以查看本文提供的原代碼。我相信你會發(fā)現(xiàn)這是一個比ASP.NET提供的原始映射更好的體驗。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com