您的位置:首页 > 编程语言 > ASP

ASP.NET 中执行 URL 重写

2007-03-24 10:49 323 查看
URL重写就是把URL地址重新改写(汗^_^)。

详情:http://www.microsoft.com/china/msdn/library/webservices/asp.net/URLRewriting.mspx

优点:把url缩短等

用法:1.下载ms的URLRewrite.dll,放到你的bin下

2.在web.config里设置如下:

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>

<configSections>

<sectionname="RewriterConfig"type="URLRewriter.Config.RewriterConfigSerializerSectionHandler,URLRewriter"/>

</configSections>

<RewriterConfig>

<Rules>

<RewriterRule>
<LookFor>~/d(/d+)/.aspx</LookFor>
<SendTo>~/default.aspx?id=$1</SendTo>
</RewriterRule>

</Rules>

</RewriterConfig>

<system.web>

<httpHandlers>

<addverb="*"path="*.aspx"

type="URLRewriter.RewriterFactoryHandler,URLRewriter"/>

</httpHandlers>

然后在cs里写:

privatevoidPage_Load(objectsender,System.EventArgse)
{
//在此处放置用户代码以初始化页面
Response.Write(Request.QueryString["id"]+"<BR>");
Response.Write("haha");
}

只要输入

localhost/,后为数字)
其实这个d123.aspx是虚拟的,并不是实际存在的。只要符合格式就行。

他就会跳到http://localhost/
overred/default.aspx

而且他在default里可以捕捉一些参数比如id,就是你的d后的数字(后必须为数字),这样你就可以显示id为123的文章。

在重写后的url里如果产生回发将会传递到d123.aspx,这样用户在点button时会看到哪个实际的地址,msdn上说的:但从用户的角度考虑,如果单击按钮时突然看到URL更改会使他们感到不安。

可见ms把客户捧为他的上帝!(真的?#¥%……—*)

继续引用ms:

出现这种情况的原因是:在呈现Web窗体时,它会将其操作属性直接设置为Request对象中文件路径的值。当然,在呈现Web窗体时,URL已从/Products/Beverages.aspx重写为ListProductsByCategory.aspx?CategoryID=1,这表明Request对象报告用户要访问ListProductsByCategory.aspx?CategoryID=1。只需使服务器端窗体不呈现操作属性即可解决此问题。(默认情况下,如果窗体不包含操作属性,浏览器将会回发。)

不幸的是,Web窗体不允许您明确指定操作属性,也不允许您设置某些属性以禁用操作属性的呈现。因此,我们必须自己来扩展System.Web.HtmlControls.HtmlForm类,覆盖RenderAttribute()方法并明确指出它不会呈现操作属性。

由于继承功能,我们可以获得HtmlForm类的所有功能,并且只需添加几行代码即可获得所需的行为。以下显示了自定义类的完整代码:

namespaceActionlessForm{
publicclassForm:System.Web.UI.HtmlControls.HtmlForm
{
protectedoverridevoidRenderAttributes(HtmlTextWriterwriter)
{
writer.WriteAttribute("name",this.Name);
base.Attributes.Remove("name");

writer.WriteAttribute("method",this.Method);
base.Attributes.Remove("method");

this.Attributes.Render(writer);

base.Attributes.Remove("action");

if(base.ID!=null)
writer.WriteAttribute("id",base.ClientID);
}
}
}

已被覆盖的RenderAttributes()方法的代码仅包含HtmlForm类的RenderAttributes()方法的准确代码,而不设置操作属性。(我使用LutzRoeder的Reflector来查看HtmlForm类的源代码。)

创建此类并对其进行编译之后,要在ASP.NETWeb应用程序中使用它,应首先将其添加到Web应用程序的References文件夹中。然后,要使用它来代替HtmlForm类,只需在ASP.NET网页的顶部添加以下内容即可:

<%@RegisterTagPrefix="skm"Namespace="ActionlessForm"
Assembly="ActionlessForm"%>

然后,将<formrunat="server">(如果有)替换为:

<skm:Formid="Form1"method="post"runat="server">

并将右边的</form>标记替换为:

</skm:Form>

以上的是继承个form,其实还有更简单的,就是继承page,这样你不需要在aspx页中改任何东西。
代码:
usingSystem;
usingSystem.IO;
usingSystem.Web;
usingSystem.Web.UI;

namespaceURl
{
/**////<summary>
///页面基类
///</summary>
publicclassOLPage:Page
{
publicOLPage()
{
}

/**////<summary>
///重写默认的HtmlTextWriter方法,修改form标记中的value属性,使其值为重写的URL而不是真实URL。
///</summary>
///<paramname="writer"></param>
protectedoverridevoidRender(HtmlTextWriterwriter)
{

if(writerisSystem.Web.UI.Html32TextWriter)
{
writer=newFormFixerHtml32TextWriter(writer.InnerWriter);
}
else
{
writer=newFormFixerHtmlTextWriter(writer.InnerWriter);
}

base.Render(writer);
}

}

internalclassFormFixerHtml32TextWriter:System.Web.UI.Html32TextWriter
{
privatestring_url;//假的URL

internalFormFixerHtml32TextWriter(TextWriterwriter):base(writer)
{
_url=HttpContext.Current.Request.RawUrl;
}

publicoverridevoidWriteAttribute(stringname,stringvalue,boolencode)
{
//如果当前输出的属性为form标记的action属性,则将其值替换为重写后的虚假URL
if(_url!=null&&string.Compare(name,"action",true)==0)
{
value=_url;
}
base.WriteAttribute(name,value,encode);
}
}

internalclassFormFixerHtmlTextWriter:System.Web.UI.HtmlTextWriter
{
privatestring_url;
internalFormFixerHtmlTextWriter(TextWriterwriter):base(writer)
{
_url=HttpContext.Current.Request.RawUrl;
}

publicoverridevoidWriteAttribute(stringname,stringvalue,boolencode)
{
if(_url!=null&&string.Compare(name,"action",true)==0)
{
value=_url;
}

base.WriteAttribute(name,value,encode);
}
}

}

你把他封装成dll,以后只要添加引用就可以拉!

ok,itissoeasy!


在ASP.NET中执行URL重写

发布日期:8/23/2004|更新日期:8/23/2004

ScottMitchell

4GuysFromRolla.com

适用范围:

Microsoft®ASP.NET

摘要:介绍如何使用MicrosoftASP.NET执行动态URL重写。URL重写是截取传入Web请求并自动将请求重定向到其他URL的过程。讨论实现URL重写的各种技术,并介绍执行URL重写的一些实际情况。

下载本文的源代码。



本页内容


引言


URL重写的常见用法


请求到达IIS时将会发生什么情况


实现URL重写


构建URL重写引擎


使用URL重写引擎执行简单的URL重写


创建真正“可删节”的URL


结论


参考资料

引言

让我们花点时间来看一下网站上的一些URL。您是否发现一些类似于http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary的URL?或者,您可能将一系列网页从一个目录或网站移动到另一个目录或网站,结果导致已将旧URL用作书签的访问者断开链接。在本文中,我们将了解如何通过将http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary替换为类似于http://yoursite.com/people/sales/chuck.smith的网址,使用URL重写将那些冗长的URL缩写为富有意义且容易记忆的URL。我们还将了解如何将URL重写用于创建智能404错误。

URL重写是截取传入Web请求并自动将请求重定向到其他资源的过程。执行URL重写时,通常会检查被请求的URL,并基于URL的值将请求重定向到其他URL。例如,在进行网站重组而将/people/目录下的所有网页移动到/info/employees/目录中时,您可能希望使用URL重写来检查Web请求是否指向了/people/目录中的文件。如果请求指向/people/目录中的文件,您可能希望自动将请求重定向到/info/employees/目录中的同一文件。

使用传统的ASP,应用URL重写的唯一方法是编写ISAPI筛选器,或者购买提供URL重写功能的第三方产品。但是,使用Microsoft®ASP.NET,您可以通过很多方法来轻松地创建您自己的URL重写软件。本文讨论了可供ASP.NET开发人员实现URL重写的各种技术,然后讨论了URL重写的一些实际使用情况。在深入讨论URL重写的技术细节之前,让我们先看一些可以使用URL重写的日常情景。



返回页首

URL重写的常见用法

创建数据驱动的ASP.NET网站时,通常会产生一个单个的网页,该网页基于查询字符串参数显示数据库数据的子集。例如,在设计电子商务站点时,您的任务之一便是允许用户浏览待售产品。为此,您可以创建一个名为displayCategory.aspx的页面,该页面将显示给定类别的产品。可以通过查询字符串参数来指定要查看的该类别的产品。也就是说,如果用户要浏览待售的Widget产品,并且所有Widget产品的CategoryID均为5,则用户可以访问以下网址:http://yousite.com/displayCategory.aspx?CategoryID=5。

创建具有此类URL的网站有两点不足:首先,从最终用户的角度考虑,URLhttp://yousite.com/displayCategory.aspx?CategoryID=5比较杂乱。可用性专家JakobNeilsen建议遵循以下标准来选择URL:

简短。

易于键入。

可以看出站点的结构。

“可删节”,允许用户通过删除URL的组成部分来浏览站点。

我还要增加一条标准,即,URL应该便于记忆。URLhttp://yousite.com/displayCategory.aspx?CategoryID=5不符合Neilsen的任何标准,也不容易记住。要求用户键入查询字符串值将使URL的键入变得非常困难,并且只有了解查询字符串参数的用途及其名称/值对结构的富有经验的Web开发人员才能够对URL进行“删节”。

较好的方法是允许使用切合实际且容易记忆的URL,如http://yoursite.com/products/Widgets。只要看一眼URL,您便可以推断出将要显示的内容--有关Widget的信息。此URL也很容易记住和共享。我可以告诉我的同事“请查看yoursite.com/products/Widgets,”,她可能无需再次问我URL是什么即可打开该页面。(尝试一下,您只需说出“Amazon.com页面”即可!)此URL还将显示出来,并且应该是“可删节”的。也就是说,如果用户删去URL的末端,键入http://yoursite.com/products,他们应该看到所有产品的列表,或者至少应该看到他们可以查看的所有类别的产品列表。
注意:要获得“可删节”URL的最好示例,可考虑使用由许多blog引擎生成的URL。要查看2004年1月28日的帖子,用户可以访问诸如http://someblog.com/2004/01/28的URL。如果该URL被删节为http://someblog.com/2004/01,用户将看到2004年1月的所有帖子。将该URL进一步删节为http://someblog.com/2004将显示2004年的所有帖子。

除了简化URL之外,URL重写还经常用于处理网站重组,以免导致大量链接断开或书签过期。



返回页首

请求到达IIS时将会发生什么情况

在正式研究URL如何实现重写之前,应首先了解Microsoft®InternetInformationServices(IIS)如何处理传入请求,这一点非常重要。当请求到达IISWeb服务器时,IIS检查被请求文件的扩展名以确定如何处理该请求。IIS可以自行处理请求(如HTML页面、图像以及其他静态内容),或者将请求路由到ISAPI扩展。(ISAPI扩展是一个处理传入Web请求的非托管编译类。其任务是生成被请求资源的内容。)

例如,当传入针对Info.asp网页的请求时,IIS会将此消息路由到asp.dllISAPI扩展。然后,该ISAPI扩展将加载被请求的ASP页面,执行该页面,并将所呈现的HTML返回给IIS,然后,IIS将该HTML发送回请求客户端。对于ASP.NET页面,IIS会将此消息路由到aspnet_isapi.dllISAPI扩展。然后,aspnet_isapi.dllISAPI扩展将处理操作传递给托管的ASP.NET辅助进程,该辅助程序将处理请求,并返回ASP.NET网页的呈现HTML。

您可以自定义IIS,以指定扩展名与ISAPI扩展的映射关系。图1显示了InternetInformationServices管理工具的“应用程序配置”对话框。请注意,与ASP.NET有关的扩展名(.aspx、ascx、config、asmx、rem、cs、vb及其他)均已映射到aspnet_isapi.dllISAPI扩展。



1.已配置的文件扩展名映射

讨论IIS如何管理传入请求稍稍超出了本文范围。但是可以在MicheleLerouxBustamante的文章InsideIISandASP.NET中找到对此内容的深入讨论。ASP.NET引擎仅处理那些扩展名已明确映射至IIS中的aspnet_isapi.dll的传入Web请求,了解这一点非常重要。

使用ISAPI筛选器检查请求

IIS除了可以将传入Web请求的文件扩展名映射到相应的ISAPI扩展之外,还将执行许多其他任务。例如,IIS将尝试对发出请求的用户进行身份验证,并确定通过身份验证的用户是否有权限访问被请求的文件。在处理请求的有效期内,IIS将经历几个状态。在每个状态下,IIS都将引发可以使用ISAPI筛选器以编程方式进行处理的事件。

与ISAPI扩展一样,ISAPI筛选器是在Web服务器上安装的非托管代码块。ISAPI扩展被设计为可以响应针对特定文件类型的请求。另一方面,ISAPI筛选器还包含可以对IIS引发的事件进行响应的代码。ISAPI筛选器可以截取甚至修改传入和传出的数据。ISAPI筛选器可以应用于很多方面,包括:

身份验证和授权。

记录和监视。

HTTP压缩。

URL重写。

虽然ISAPI筛选器可用于执行URL重写,但本文将讨论如何使用ASP.NET实现URL重写。不过,我们将对使用ISAPI筛选器与使用ASP.NET中的技术实现URL重写进行权衡。

请求进入ASP.NET引擎时将会发生什么情况

在ASP.NET之前,需要使用ISAPI筛选器来实现IISWeb服务器上的URL重写。由于ASP.NET引擎与IIS非常相似,因此可以使用ASP.NET进行URL重写。存在相似之处的原因在于ASP.NET引擎可以实现以下功能:

在处理请求时可以引发事件。

允许任意数量的HTTP模块处理所引发的事件,这与IIS的ISAPI筛选器相似。

将呈现被请求资源这项任务委托给HTTP处理程序,该处理程序与IIS的ISAPI扩展相似。

与IIS一样,ASP.NET引擎在请求的有效期内将会触发事件,通过发信号来表示其处理过程从一个状态改变为了另一个状态。例如,当ASP.NET引擎首次响应请求时,BeginRequest事件将被触发。接下来触发的是AuthenticateRequest事件,该事件在已建立用户标识时出现。(此外,还有大量的其他事件:AuthorizeRequestResolveRequestCacheEndRequest,等等。这些事件属于System.Web.HttpApplication类;有关详细信息,请参阅位于以下网址的技术文档:HttpApplicationClassOverview。)

正如上一部分所讨论的,可以创建ISAPI筛选器以响应IIS引发的事件。同样,ASP.NET提供了HTTP模块,该模块可以响应由ASP.NET引擎引发的事件。可以将ASP.NETWeb应用程序配置为具有多个HTTP模块。对于由ASP.NET引擎处理的每个请求,将初始化每个已配置的HTTP模块,并允许将事件处理程序绑定到处理请求期间所引发的事件。请注意,对每个请求均使用了许多内置HTTP模块。其中的一个内置HTTP模块是FormsAuthenticationModule,该模块首先检查是否使用了窗体身份验证,如果使用,将检查是否对用户进行了身份验证。如果没有使用,会自动将用户重定向到指定的登录页面。

如上所述,通过使用IIS,传入请求将最终发送给ISAPI扩展,而ISAPI扩展的任务是返回特定请求的数据。例如,在请求传统的ASP网页时,IIS将请求传递给asp.dllISAPI扩展,该扩展的任务是返回被请求的ASP页面的HTML标记。ASP.NET引擎使用相似的方法。初始化HTTP模块后,ASP.NET引擎的下一项任务是确定应由哪个HTTP处理程序来处理请求。

所有通过ASP.NET引擎传递的请求最终都将到达HTTP处理程序或HTTP处理程序工厂(HTTP处理程序工厂仅返回HTTP处理程序的实例,然后使用该实例来处理请求)。最终的HTTP处理程序将返回响应,即呈现被请求的资源。此响应将被发送回IIS,然后IIS将响应返回给提出请求的用户。

ASP.NET包括许多内置的HTTP处理程序。例如,PageHandlerFactory用于呈现ASP.NET网页。WebServiceHandlerFactory用于呈现ASP.NETWeb服务的响应SOAP信封。TraceHandler将向trace.axd呈现请求的HTML标记。

图2描述了如何处理对ASP.NET资源的请求。首先,IIS接收到请求,并将请求调度给aspnet_isapi.dll。接下来,ASP.NET引擎对已配置的HTTP模块进行初始化。最后将调用正确的HTTP处理程序,并呈现被请求的资源,将所生成的标记返回给IIS和请求客户端。



2.IIS和ASP.NET正在处理请求

创建和注册自定义HTTP模块和HTTP处理程序

创建自定义HTTP模块和HTTP处理程序是相对简单的任务,包括创建实现正确接口的托管类。HTTP模块必须实现System.Web.IHttpModule接口,而HTTP处理程序和HTTP处理程序工厂必须分别实现System.Web.IHttpHandler接口和System.Web.IHttpHandlerFactory接口。创建HTTP处理程序和HTTP模块的细节超出了本文的范围。要获得详细的背景知识,请阅读MansoorAhmedSiddiqui的文章HTTPHandlersandHTTPModulesinASP.NET。

创建了自定义HTTP模块或HTTP处理程序之后,必须将其注册到Web应用程序。为整个Web服务器注册HTTP模块和HTTP处理程序仅需在machine.config文件中进行简单的添加即可;而为特定Web应用程序注册HTTP模块或HTTP处理程序包括向应用程序的Web.config文件中添加几行XML。

特别要说明的是,要将HTTP模块添加到Web应用程序,应在Web.config的configuration/system.web部分添加以下几行:

<httpModules><addtype="type"name="name"/></httpModules>

type值提供了HTTP模块的程序集和类名称,而name值提供了友好名称,可以在Global.asax文件中使用此友好名称来引用HTTP模块。

Web.config的configuration/system.web部分中的<httpHandlers>标记对HTTP处理程序和HTTP处理程序工厂进行了配置,如下所示:

<httpHandlers><addverb="verb"path="path"type="type"/></httpHandlers>

如上所述,对于每个传入请求,ASP.NET引擎将确定应使用哪个HTTP处理程序来呈现请求。此决定是基于传入请求的动词和路径做出的。动词将指定所作出的HTTP请求的类型(GET或POST),而路径将指定被请求文件的位置和文件名。因此,如果我们希望HTTP处理程序处理对扩展名为.scott的文件的所有请求(GET或POST),可以在Web.config文件中添加下面几行:

<httpHandlers><addverb="*"path="*.scott"type="type"/></httpHandlers>

其中,type是HTTP处理程序的类型。

注意:注册HTTP处理程序时,应确保HTTP处理程序使用的扩展名已从IIS映射到ASP.NET引擎,这一点非常重要。也就是说,在本.scott示例中,如果.scott扩展名没有从IIS映射到aspnet_isapi.dllISAPI扩展,则对文件foo.scott的请求将导致IIS试图返回文件foo.scott的内容。为了使HTTP处理程序可以处理此请求,必须将.scott扩展名映射到ASP.NET引擎。然后,ASP.NET引擎将把请求正确地路由到相应的HTTP处理程序。

有关注册HTTP模块和HTTP处理程序的详细信息,请务必参考<httpModules>elementdocumentation和<httpHandlers>elementdocumentation。



返回页首

实现URL重写

可以使用ISAPI筛选器在IISWeb服务器级别实现URL重写,也可以使用HTTP模块或HTTP处理程序在ASP.NET级别实现URL重写。本文重点介绍如何使用ASP.NET实现URL重写,因此我们将不对使用ISAPI筛选器实现URL重写的细节进行深入探讨。但是,有大量的第三方ISAPI筛选器可用于URL重写,例如:

ISAPIRewrite

IISRewrite

PageXChanger

还有许多其他的筛选器!

通过System.Web.HttpContext类的RewritePath()方法,可以在ASP.NET级别实现URL重写。HttpContext类包含有关特定HTTP请求的HTTP特定信息。对于ASP.NET引擎收到的每个请求,均为该请求创建一个HttpContext实例。此类具有如下属性:RequestResponse,提供对传入请求和传出响应的访问;ApplicationSession,提供对应用程序和会话变量的访问;User,提供有关通过了身份验证的用户的信息;其他相关属性。

使用Microsoft®.NETFrameworkVersion1.0,RewritePath()方法可以接受单个字符串作为要使用的新路径。HttpContext类的RewritePath(string)方法在内部对Request对象的Path属性和QueryString属性进行更新。除了RewritePath(string),.NETFramework1.1还包括另一种形式的RewritePath()方法,此方法可以接受三个字符串输入参数。此备用重载形式不仅要设置Request对象的Path属性和QueryString属性,还要设置内部成员变量,这些变量用于计算Request对象的PhysicalPathPathInfoFilePath属性值。

要在ASP.NET中实现URL重写,需要创建HTTP模块或HTTP处理程序,以便完成以下操作:

1.

检查被请求的路径,以确定URL是否需要重写。

2.

如果需要重写,通过调用RewritePath()方法来重写路径。

例如,假设我们的网站中包含每个员工通过/info/employee.aspx?empID=employeeID均可访问的信息。为了使URL可以更多地被“删节”,我们可以决定通过以下地址来访问员工页面:/people/EmployeeName.aspx。这就是我们要使用URL重写的一个例子。也就是说,在请求/people/ScottMitchell.aspx页面时,我们要重写该URL,以便使用/info/employee.aspx?empID=1001页面。

使用HTTP模块执行URL重写

在ASP.NET级别执行URL重写时,可以使用HTTP模块或HTTP处理程序来执行重写。使用HTTP模块时,必须决定在请求有效期内的哪个时间点上来检查URL是否需要重写。乍一看,这似乎可以任意选择,但决定会以一种明显而微妙的方式对应用程序产生影响。由于内置ASP.NETHTTP模块使用Request对象的属性执行任务,因此选择在何处执行重写非常重要。(如上所述,重写路径将改变Request对象的属性值。)下面列出了这些密切相关的内置HTTP模块及其捆绑到的事件:

HTTP模块事件说明
FormsAuthenticationModule
AuthenticateRequest
确定用户是否通过了窗体身份验证。如果没有,用户将被自动重定向到指定的登录页面。
FileAuthorizationMoudle
AuthorizeRequest
使用Windows身份验证时,此HTTP模块将检查以确保Microsoft®Windows®帐户对被请求的资源具有足够的权限。
UrlAuthorizationModule
AuthorizeRequest
检查以确保请求者可以访问指定的URL。通过Web.config文件中的<authorization>和<location>元素来指定URL授权。
如上所述,BeginRequest事件在AuthenticateRequest之前触发,后者在AuthenticateRequest之前触发。

可以执行URL重写的一个安全位置是在BeginRequest事件中。也就是说,如果URL需要重写,该操作将在任何一个内置HTTP模块运行后执行。使用窗体身份验证时,这种方法存在一定的缺陷。如果您以前使用过窗体身份验证,您会了解当用户访问受限资源时,他们将被自动重定向到指定的登录页面。成功登录后,用户将被返回到他们第一次尝试访问的页面。

如果在BeginRequestAuthenticateRequest事件中执行URL重写,登录页面(提交后)将把用户重定向到重写后的页面上。也就是说,假设用户在其浏览窗口中键入了/people/ScottMitchell.aspx,此地址将被重写为/info/employee.aspx?empID=1001。如果将Web应用程序配置为使用窗体身份验证,当用户第一次访问/people/ScottMitchell.aspx时,首先,URL将被重写为/info/employee.aspx?empID=1001;接下来,FormsAuthenticationModule将运行,并将用户重定向到登录页面(如果需要)。但是,用户在成功登录后将被发送到/info/employee.aspx?empID=1001,因为当FormsAuthenticationModule运行后,此URL即是请求的URL。

同样,在BeginRequestAuthenticateRequest事件中执行重写时,UrlAuthorizationModule看到的将是重写后的URL。也就是说,如果您在Web.config文件中使用<location>元素来为特定的URL指定授权,则必须引用重写后的URL。

要解决这些细微问题,您可以决定在AuthorizeRequest事件中执行URL重写。此方法解决了URL授权和窗体身份验证的一些问题,但同时也产生了新的问题:文件授权无法工作。使用Windows身份验证时,FileAuthorizationModule将检查以确保通过身份验证的用户具有访问特定ASP.NET页面的相应权限。

假设一组用户对C:/Inetput/wwwroot/info/employee.aspx没有Windows级别的文件访问权限,并要尝试访问/info/employee.aspx?empID=1001,他们将会收到授权错误消息。但是,如果我们将URL重写移到AuthenticateRequest事件中,当FileAuthorizationModule检查安全设置时,仍然认为被请求的文件是people/ScottMitchell.aspx,因为该URL必须被重写。因此,文件授权检查将通过,允许此用户查看重写后的URL/info/employee.aspx?empID=1001的内容。

那么,应该何时在HTTP模块中执行URL重写?这取决于要使用的身份验证类型。如果不想使用任何身份验证,则无论URL重写发生在BeginRequestAuthenticateRequest还是AuthorizeRequest中都没有什么关系。如果要使用窗体身份验证而不使用Windows身份验证,请将URL重写放在AuthorizeRequest事件处理程序中执行。最后,如果要使用Windows身份验证,请在BeginRequestAuthenticateRequest事件进行过程中安排URL重写。

在HTTP处理程序中执行URL重写

也可以由HTTP处理程序或HTTP处理程序工厂执行URL重写。如上所述,HTTP处理程序是负责生成特定类型请求的内容的类;HTTP处理程序工厂是负责返回HTTP处理程序实例的类,该实例可以生成特定类型请求的内容。

在本文中,我们将对如何为ASP.NET网页创建URL重写HTTP处理程序工厂进行讨论。HTTP处理程序工厂必须实现IHttpHandlerFactory接口,此接口包括GetHandler()方法。初始化相应的HTTP模块后,ASP.NET引擎将确定为给定的请求调用哪个HTTP处理程序或HTTP处理程序工厂。如果要调用HTTP处理程序工厂,ASP.NET引擎将为Web请求调用传入HttpContext的HTTP处理程序工厂的GetHandler()方法,以及一些其他信息。然后,HTTP处理程序工厂必须返回一个对象,该对象将实现可以处理请求的IHttpHandler

要通过HTTP程序程序执行URL重写,我们可以创建一个HTTP处理程序工厂,该处理程序工厂的GetHandler()方法将检查被请求的路径,以确定是否需要重写URL。如果需要,它可以调用传入的HttpContext对象的RewritePath()方法,如前面所讨论的。最后,HTTP处理程序工厂可以返回由System.Web.UI.PageParser类的GetCompiledPageInstance()方法返回的HTTP处理程序。(此技术与内置ASP.NET网页HTTP处理程序工厂PageHandlerFactory工作时所应用的技术相同。)

由于所有HTTP模块都将在实例化自定义HTTP处理程序工厂之前进行初始化,因此,在将URL重写放在事件的后半段时,使用HTTP处理程序工厂就会带来相同的风险,即文件授权无法工作。因此,如果您依赖于Windows身份验证和文件授权,您可能希望为URL重写使用HTTP模块方法。

在下一部分中,我们将对构建可重用的URL重写引擎进行讨论。在介绍了URL重写引擎(可通过下载本文的代码获得)之后,我们将在剩下的两个部分中对URL重写的实际使用情况进行介绍。首先,我们将讨论如何使用URL重写引擎,并介绍一个简单的URL重写示例。接下来,我们将利用重写引擎的正则表达式功能来提供真正“可删节”的URL。



返回页首

构建URL重写引擎

为了有助于描述如何在ASP.NETWeb应用程序中实现URL重写,我创建了URL重写引擎。此重写引擎将提供以下功能:

使用URL重写引擎的ASP.NET页面开发人员可以在Web.config文件中指定重写规则。

重写规则可以使用正则表达式来实现功能强大的重写规则。

可以轻松地将URL重写配置为使用HTTP模块或HTTP处理程序。

在本文中,我们将介绍仅使用HTTP模块的URL重写。要查看如何使用HTTP处理程序来执行URL重写,请参考可随本文下载的代码。

为URL重写引擎指定配置信息

让我们先介绍一下Web.config文件中重写规则的结构。首先,您需要在Web.config文件中指明要使用HTTP模块还是HTTP处理程序来执行URL重写。在下载代码中,Web.config文件包含两个已注释掉的条目:

<!--<httpModules><addtype="URLRewriter.ModuleRewriter,URLRewriter"name="ModuleRewriter"/></httpModules>--><!--<httpHandlers><addverb="*"path="*.aspx"type="URLRewriter.RewriterFactoryHandler,URLRewriter"/></httpHandlers>-->

注释掉<httpModules>条目,以使用HTTP模块执行重写;注释掉<httpHandlers>条目,以使用HTTP处理程序执行重写。

除了指定使用HTTP模块还是HTTP处理程序执行重写外,Web.config文件还包含重写规则:重写规则由两个字符串组成:要在被请求的URL中查找的模式;要替换此模式的字符串(如果找到)。在Web.config文件中,此信息是使用以下语法表达的:

<RewriterConfig><Rules><RewriterRule><LookFor>要查找的模式</LookFor><SendTo>要用来替换模式的字符串</SendTo></RewriterRule><RewriterRule><LookFor>要查找的模式</LookFor><SendTo>要用来替换模式的字符串</SendTo></RewriterRule>...</Rules></RewriterConfig>

每个重写规则均由<RewriterRule>元素表达。要搜索的模式由<LookFor>元素指定,而要替换所找到的模式的字符串将在<SentTo>元素中输入。这些重写规则将从头到尾进行计算。如果发现与某个规则匹配,URL将被重写,并且对重写规则的搜索将会终止。

在<LookFor>元素中指定模式时,请注意,要使用正则表达式来执行匹配和字符串替换。(稍后,我们将介绍一个真实的示例,说明如何使用正则表达式来搜索模式。)由于模式是正则表达式,应确保转义正则表达式中的任何保留字符。(一些正则表达式保留字符包括:.、?、^、$及其他。可以通过在前面加反斜杠(如/.)对这些字符进行转义,以匹配文字句点。)

使用HTTP模块执行URL重写

创建HTTP模块与创建可以实现IHttpModule接口的类一样简单。IHttpModule接口定义了两种方法:

Init(HttpApplication)。此方法在初始化HTTP模块后触发。在此方法中,您将把事件处理程序绑定到相应的HttpApplication事件。

Dispose()。当请求已完成并已发送回IIS时调用此方法。您应当在此处执行所有最终的清除操作。

为了便于为URL重写创建HTTP模块,我将从创建抽象基类BaseModuleRewriter开始介绍。此类将实现IHttpModule。在Init()事件中,它将HttpApplicationAuthorizeRequest事件绑定到BaseModuleRewriter_AuthorizeRequest方法。BaseModuleRewriter_AuthorizeRequest方法将调用该类传入被请求的PathRewrite()方法,以及传入Init()方法的HttpApplication对象。Rewrite()方法是抽象的,也就是说,在BaseModuleRewriter类中,Rewrite()方法没有方法主体;从BaseModuleRewriter派生而来的类必须覆盖此方法并提供方法主体。

具有此基类后,只需创建由BaseModuleRewriter派生的类即可,该类可以覆盖Rewrite()并在那里执行URL重写逻辑。下面显示了BaseModuleRewriter的代码。

publicabstractclassBaseModuleRewriter:IHttpModule{publicvirtualvoidInit(HttpApplicationapp){//警告!此代码不适用于Windows身份验证!//如果使用Windows身份验证,//请改为app.BeginRequestapp.AuthorizeRequest+=newEventHandler(this.BaseModuleRewriter_AuthorizeRequest);}publicvirtualvoidDispose(){}protectedvirtualvoidBaseModuleRewriter_AuthorizeRequest(objectsender,EventArgse){HttpApplicationapp=(HttpApplication)sender;Rewrite(app.Request.Path,app);}protectedabstractvoidRewrite(stringrequestedPath,HttpApplicationapp);}

请注意,BaseModuleRewriter类将在AuthorizeRequest事件中执行URL重写。如上所述,如果将Windows身份验证与文件授权结合使用,您需要对此做出更改,以便可以在BeginRequestAuthenticateRequest事件中执行URL重写。

ModuleRewriter类扩展了BaseModuleRewriter类,并负责执行实际的URL重写。ModuleRewriter包含单一覆盖方法(Rewrite()),如下所示:

protectedoverridevoidRewrite(stringrequestedPath,System.Web.HttpApplicationapp){//获得配置规则RewriterRuleCollectionrules=RewriterConfiguration.GetConfig().Rules;//遍历每个规则...for(inti=0;i<rules.Count;i++){//获得要查找的模式,并且//解析Url(转换为相应的目录)stringlookFor="^"+RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath,rules[i].LookFor)+"___FCKpd___6quot;;//创建regex(请注意,已设置IgnoreCase...)Regexre=newRegex(lookFor,RegexOptions.IgnoreCase);//查看是否找到了匹配的规则if(re.IsMatch(requestedPath)){//找到了匹配的规则--进行必要的替换stringsendToUrl=RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath,re.Replace(requestedPath,rules[i].SendTo));//重写URLRewriterUtils.RewriteUrl(app.Context,sendToUrl);break;//退出For循环}}}

Rewrite()方法从获取Web.config文件中的一组重写规则开始。然后,它将遍历重写规则,每次遍历一个,对于每个规则,它将获取规则的LookFor属性,并使用正则表达式来确定是否在被请求的URL中找到了匹配的规则。

如果找到了匹配的规则,将在具有SendTo属性值的被请求路径上执行正则表达式替换。然后,替换后的URL将被传递到RewriterUtils.RewriteUrl()方法中。RewriterUtils是一个helper类,此类将提供一对由URL重写HTTP模块和HTTP处理程序使用的静态方法。RewriterUrl()方法仅调用HttpContext对象的RewriteUrl()方法。

注意:您可能已注意到,执行正则表达式匹配和替换时,将调用RewriterUtils.ResolveUrl()。此helper方法只替换具有应用程序路径值的字符串中的所有~实例。

URL重写引擎的整个代码可随本文下载。我们已经介绍了大部分密切相关的组件,但还有一些其他组件(例如,对Web.config文件中XML格式的重写规则进行反序列化以使其成为对象的类),以及用于URL重写的HTTP处理程序工厂。本文剩余的三个部分将对URL重写的实际使用情况进行介绍。



返回页首

使用URL重写引擎执行简单的URL重写

为了实际演示URL重写引擎,我们来构建一个使用简单URL重写的ASP.NETWeb应用程序。假设我们所工作的公司通过网络销售分类产品。这些产品分为以下几个类别:

类别ID类别名称
1
饮料
2
调味品
3
糖果
4
奶制品
...
...
假设我们已创建了名为ListProductsByCategory.aspx的ASP.NET网页,该网页在查询字符串中接受类别ID值,并显示属于该类的所有产品。因此,要查看我们销售的饮料的用户可以访问ListProductsByCategory.aspx?CategoryID=1,而那些要查看奶制品的用户可以访问ListProductsByCategory.aspx?CategoryID=4。此外,还假设我们有一个名为ListCategories.aspx的页面,该页面列出了待售的所有产品类别。

很显然,这是一个URL重写事例,因为提供给用户的URL没有为用户带来任何意义,也没有为他们提供任何“可删节性”。因此,让我们使用URL重写,以便在用户访问/Products/Beverages.aspx时,他们的URL将被重写为ListProductsByCategory.aspx?CategoryID=1。我们可以在Web.config文件中使用以下URL重写规则来实现此功能。

<RewriterConfig><Rules><!--产品制表者规则--><RewriterRule><LookFor>~/Products/Beverages/.aspx</LookFor><SendTo>~/ListProductsByCategory.aspx?CategoryID=1</SendTo></RewriterRule><RewriterRule></Rules></RewriterConfig>

正如您可以看到的,此规则将进行搜索,以查看用户请求的路径是否为/Products/Beverages.aspx。如果是,它便将URL重写为/ListProductsByCategory.aspx?CategoryID=1。

注意:请注意,<LookFor>元素对Beverages.aspx中的句点进行了转义。这是因为在正则表达式模式中使用了<LookFor>值,并且句点是正则表达式中的特殊字符,该字符表示“匹配任意字符”,例如,与URL/Products/BeveragesQaspx匹配。通过转义句点(使用/.),可以表明我们要匹配的是文字句点,而不是任何旧的字符。

有了此规则之后,当用户访问/Products/Beverages.aspx时,页面上将显示待售的饮料。图3显示了访问/Products/Beverages.aspx的浏览器的快照。请注意,在浏览器的地址栏中,URL将读取/Products/Beverages.aspx,但用户实际看到的是ListProductsByCategory.aspx?CategoryID=1的内容。(实际上,Web服务器上根本不存在/Products/Beverages.aspx文件!)



图3.重写URL之后请求类别

与/Products/Beverages.aspx相似,下面我们要为其他产品类别添加重写规则。此操作仅包括在Web.config文件的<Rules>元素内添加附加的<RewriterRule>元素。请参阅下载内容中的Web.config文件,以获取用于此演示的一组完整的重写规则。

为了使URL更具可删节性,最好使用户只需从/Products/Beverages.aspx中删除Beverages.aspx即可看到产品类别的列表。乍一看,这可能是一项很普通的任务(只需添加一个将/Products/映射到/ListCategories.aspx的重写规则即可)。但此操作存在一个微妙之处,即您必须首先创建一个/Products/目录,并在/Products/目录中添加一个空的Default.aspx文件。

要理解需要执行这些额外步骤的原因,可以参考前面的内容,即URL重写引擎位于ASP.NET级别上。也就是说,如果ASP.NET引擎永远没有机会处理请求,URL重写引擎就没有办法检测传入的URL。而且,请记住,仅当被请求的文件具有相应的扩展名时,IIS才会将传入请求传递给ASP.NET引擎。因此,如果用户访问/Products/,而IIS没有看到任何文件扩展名,那么它将检查目录,以查看是否存在这样一个文件,即该文件名为默认文件名中的一个。(Default.aspx、Default.htm、Default.asp等等。“IIS管理”对话框中“Web服务器属性”对话框的“文档”选项卡对这些默认文件名进行了定义。)当然,如果/Products/目录不存在,IIS将返回HTTP404错误。

因此,我们需要创建/Products/目录。另外,我们还需要在此目录中创建一个文件Default.aspx。这样,当用户访问/Products/时,IIS将检测目录,查看是否存在一个名为Default.aspx的文件,然后将处理过程传递给ASP.NET引擎。然后,URL重写器将在重写URL时分解。

创建目录和Default.aspx文件后,请继续操作,并向<Rules>元素中添加以下重写规则:

<RewriterRule><LookFor>~/Products/Default/.aspx</LookFor><SendTo>~/ListCategories.aspx</SendTo></RewriterRule>

有了此规则之后,当用户访问/Products/或/Products/Default.aspx时,他们将看到产品类别列表,如图4所示。



4.向URL添加“可删节性”

处理回发

如果要重写的URL中包含一个服务器端的Web窗体并执行回发,则窗体回发后,将使用带下划线的URL。也就是说,如果用户在浏览器中输入/Products/Beverages.aspx,他们在浏览器地址栏中看到的将是/Products/Beverages.aspx,但是他们看到的内容将是ListProductsByCategory.aspx?CategoryID=1的内容。如果ListProductsByCategory.aspx执行了回发,用户将被回发到ListProductsByCategory.aspx?CategoryID=1,而不是/Products/Beverages.aspx。这样不会中断任何内容,但从用户的角度考虑,如果单击按钮时突然看到URL更改会使他们感到不安。

出现这种情况的原因是:在呈现Web窗体时,它会将其操作属性直接设置为Request对象中文件路径的值。当然,在呈现Web窗体时,URL已从/Products/Beverages.aspx重写为ListProductsByCategory.aspx?CategoryID=1,这表明Request对象报告用户要访问ListProductsByCategory.aspx?CategoryID=1。只需使服务器端窗体不呈现操作属性即可解决此问题。(默认情况下,如果窗体不包含操作属性,浏览器将会回发。)

不幸的是,Web窗体不允许您明确指定操作属性,也不允许您设置某些属性以禁用操作属性的呈现。因此,我们必须自己来扩展System.Web.HtmlControls.HtmlForm类,覆盖RenderAttribute()方法并明确指出它不会呈现操作属性。

由于继承功能,我们可以获得HtmlForm类的所有功能,并且只需添加几行代码即可获得所需的行为。以下显示了自定义类的完整代码:

namespaceActionlessForm{publicclassForm:System.Web.UI.HtmlControls.HtmlForm{protectedoverridevoidRenderAttributes(HtmlTextWriterwriter){writer.WriteAttribute("name",this.Name);base.Attributes.Remove("name");writer.WriteAttribute("method",this.Method);base.Attributes.Remove("method");this.Attributes.Render(writer);base.Attributes.Remove("action");if(base.ID!=null)writer.WriteAttribute("id",base.ClientID);}}}

已被覆盖的RenderAttributes()方法的代码仅包含HtmlForm类的RenderAttributes()方法的准确代码,而不设置操作属性。(我使用LutzRoeder的Reflector来查看HtmlForm类的源代码。)

创建此类并对其进行编译之后,要在ASP.NETWeb应用程序中使用它,应首先将其添加到Web应用程序的References文件夹中。然后,要使用它来代替HtmlForm类,只需在ASP.NET网页的顶部添加以下内容即可:

<%@RegisterTagPrefix="skm"Namespace="ActionlessForm"Assembly="ActionlessForm"%>

然后,将<formrunat="server">(如果有)替换为:

<skm:Formid="Form1"method="post"runat="server">

并将右边的</form>标记替换为:

</skm:Form>

您可以在ListProductsByCategory.aspx(包含在本文的下载代码中)中发现操作中的此自定义WebForm类。下载内容中还包含了用于无操作WebForm的VisualStudio.NET项目。

注意:如果要重写的目标URL没有执行回发,则无需使用此自定义WebForm类。



返回页首

创建真正“可删节”的URL

前一部分中介绍的简单URL重写显示了如何轻松地为URL重写引擎配置新的重写规则。但在使用正则表达式时,重写规则的真正功能才会发挥更大作用,本部分将对此进行探讨。

Blog在当今正变得越来越流行,似乎每个人都拥有自己的blog。如果您不熟悉blog:blog是经常更新的个人页面,通常作为联机期刊。大多数blog只记录每天发生的事情,还有一些blog可能关注于特定的主题(例如,电影回顾、体育团队或计算机技术)。

可以在任何地点对blog进行更新,更新频率为从每天几次到每周一次或两次,具体情况取决于作者。通常,blog主页将显示最近的10个条目,但实际上,所有blog软件均提供存档,访问者可以通过存档读取较早的帖子。Blog是用于“可删节”URL的一个功能强大的应用程序。假设在搜索blog的存档时,您在URL/2004/02/14.aspx上发现了您自己。如果您发现自己在阅读2004年2月14日的帖子,您是否觉得很惊讶?而且,您可能希望查看2004年2月的所有帖子,在这种情况下,您可以尝试将URL删节为/2004/02/。要查看2004年的所有帖子,您可以尝试访问/2004/。

维护blog时,最好为访问者提供此级别的URL“可删节性”。许多blog引擎都提供此功能,但我们将讨论如何使用URL重写来实现此功能。

首先,我们需要一个ASP.NET网页,此页面将按照日、月或年来显示blog条目。假设我们有一个ShowBlogContent.aspx页面,该页面的查询字符串参数为年、月和日。要查看2004年2月14日的帖子,我们可以访问ShowBlogContent.aspx?year=2004&month=2&day=14。要查看2004年2月的所有帖子,我们可以访问ShowBlogContent.aspx?year=2004&month=2。最后,要查看2004年的所有帖子,我们可以浏览到ShowBlogContent.aspx?year=2004。(可以在本文的下载内容中找到ShowBlogContent.aspx的代码。)

在这种情况下,如果用户访问/2004/02/14.aspx,我们需要将URL重写为ShowBlogContent.aspx?year=2004&month=2&day=14。所有三种情况(URL指定了年、月和日时;URL仅指定了年和月时;URL仅指定了年时)均可使用重写规则进行处理:

<RewriterConfig><Rules><!--Blog内容显示程序规则--><RewriterRule><LookFor>~/(/d{4})/(/d{2})/(/d{2})/.aspx</LookFor><SendTo>~/ShowBlogContent.aspx?year=$1&month=$2&day=$3</SendTo></RewriterRule><RewriterRule><LookFor>~/(/d{4})/(/d{2})/Default/.aspx</LookFor><SendTo><![CDATA[~/ShowBlogContent.aspx?year=$1&month=$2]]></SendTo></RewriterRule><RewriterRule><LookFor>~/(/d{4})/Default/.aspx</LookFor><SendTo>~/ShowBlogContent.aspx?year=$1</SendTo></RewriterRule></Rules></RewriterConfig>

这些重写规则表明了正则表达式的功能。在第一个规则中,我们使用模式(/d{4})/(/d{2})/(/d{2})/.aspx查找URL。在简明英语中,它对应了这样一个字符串:首先是四个数字,后跟一个斜杠,然后是两个数字,后跟一个斜杠,然后再跟两个数字,最后是一个.aspx。每个数字组周围的括号非常重要,通过它可以在相应的<SendTo>属性中引用这些括号内的匹配字符。特别是,我们可以针对第一、第二和第三个括号组分别使用$1、$2和$3引用回括号内的匹配组。

注意:由于Web.config文件采用XML格式,但是必须对元素文字部分中的字符(如&、<和>)进行转义。在第一个规则的<SendTo>元素中,&被转义为&。在第二个规则的<SendTo>中使用了另外一种技术(使用<![CDATA[...]]>元素),无需对内部的内容进行转义。可以使用两种方法中的任何一种,并且都会得到相同的结果。

图5、6和7显示了操作中的URL重写。数据实际上是从我的bloghttp://scottonwriting.net/中拖过来的。图5中显示了2003年11月7日的帖子;图6中显示了2003年11月的所有帖子;图7显示了2003年的所有帖子。



5.2003年11月7日的帖子



6.2003年11月的所有帖子



7.2003年的所有帖子

注意:URL重写引擎在<LookFor>元素中需要使用正则表达式模式。如果您对正则表达式不熟悉,可以阅读我在早些时候编写的一篇文章AnIntroductiontoRegularExpressions。另外,还有一个很好的网站:RegExLib.com,在那里您可以获取有关常用正则表达式的帮助信息,还可以共享您自己的自定义正则表达式。

构建必备的目录结构

当请求/2004/03/19.aspx时,IIS将通知.aspx扩展,并将请求路由到ASP.NET引擎。请求在ASP.NET引擎的管道中移动时,URL将被重写为ShowBlogContent.aspx?year=2004&month=03&day=19,并且访问者会看到2004年3月19日的blog条目。但是当用户浏览到/2004/03/时将会发生什么情况呢?除非有一个/2004/03/目录,否则IIS将返回一个404错误。此外,此目录中还需要具有Default.aspx页面,以便可以将请求传递给ASP.NET引擎。

因此,要使用这种方法,必须手动创建一个用于每年的目录(其中包含blog条目),并且目录中具有一个Default.aspx页面。另外,在每年目录中,您需要再手动创建十二个目录(01、02、?、?...、12),并且每个目录中均有一个Default.aspx文件。(如上所述,我们还必须执行前面演示中的操作,即在/Products/目录中添加一个Default.aspx文件,以便访问/Products/时可以正确显示ListCategories.aspx。)

很显然,添加这样一个目录结构可能是一件很痛苦的事情。解决此问题的方法是使所有传入的IIS请求都映射到ASP.NET引擎。通过这种方法,即使访问URL/2004/03/,IIS也会如实地将请求传递给ASP.NET引擎(即使并不存在/2004/03/目录)。但是,使用这种方法将使ASP.NET引擎负责处理到达Web服务器的所有类型的传入请求,包括图像、CSS文件、外部JavaScript文件、MacromediaFlash文件,等等。

对处理所有文件类型的全面讨论远远超出了本文的范围。有关使用此技术的ASP.NETWeb应用程序的示例,请参阅.Text,一个开放源blog引擎。.Text可以配置为将所有请求均映射到ASP.NET引擎。它可以使用自定义HTTP处理程序来处理生成所有文件类型的问题,自定义HTTP处理程序了解如何生成典型的静态文件类型(图像、CSS文件,等等)。



返回页首

结论

在本文中,我们讨论了如何在ASP.NET级别通过HttpContext类的RewriteUrl()方法来执行URL重写。正如我们所看到的,RewriteUrl()更新了特定的HttpContext'sRequest属性,从而更新了被请求的文件和路径。最终结果是,从用户角度来看,他们要访问某个特定的URL,但从Web服务器端来看,被请求的却是另一个URL。

可以在HTTP模块或HTTP处理程序中重写URL。在本文中,我们介绍了如何使用HTTP模块执行重写,并讨论了在管道中的不同阶段执行重写的结果。

当然,如果执行ASP.NET级别的重写,则仅当已成功地将请求从IIS传递给ASP.NET引擎后才会发生URL重写。实际上,只有用户请求带.aspx扩展名的页面时才会出现这种情况。但是,如果您要使用户可以进入实际并不存在的URL,但又希望重写到现有的ASP.NET页面,则必须创建虚拟目录和Default.aspx页面,或者对IIS进行配置,以使所有传入请求一律被路由到ASP.NET引擎。



返回页首

参考资料

ASP.NET:Tips,Tutorials,andCode

MicrosoftASP.NETCodingStrategieswiththeMicrosoftASP.NETTeam

EssentialASP.NETwithExamplesinC#

参考资料

URL重写是涉及到ASP.NET和竞争服务器端Web技术的一个主题。例如,ApacheWeb服务器提供了名为mod_rewrite的URL重写模块。Mod_rewrite是一个功能强大的重写引擎,提供了基于条件(如HTTP标题和服务器变量)的重写规则以及使用正则表达式的重写规则。有关mod_rewrite的详细信息,请查阅AUser'sGuidetoURLRewritingwiththeApacheWebServer。

还有许多有关使用ASP.NET执行URL重写的文章。Rewrite.NET-AURLRewritingEnginefor.NET对创建模拟mod_rewrite正则表达式规则的URL重写引擎进行了介绍。URLRewritingWithASP.NET为ASP.NET的URL重写功能提供了很好的概述。IanGriffiths包含一个blogentry,介绍了有关使用ASP.NET进行URL重写的一些注意事项(如在本文中讨论过的回发问题)。FabriceMarguerie(readmore)和JasonSalas(readmore)具有有关使用URL重写来增强搜索引擎定位功能的blog条目。

作者简介

ScottMitchell著有五本书,他还是4GuysFromRolla.com网站的创建者。在过去五年中,他一直从事MicrosoftWeb技术方面的研究工作。Scott是一位独立的顾问、培训师和作家。您可以通过mitchell@4guysfromrolla.com与作者进行联络,或者通过作者的blog进行联络,其网址是:http://scottonwriting.net/。

©2004MicrosoftCorporation版权所有。保留所有权利。使用规定。

使用URL重写引擎执行简单的URL重写

为了实际演示URL重写引擎,我们来构建一个使用简单URL重写的ASP.NETWeb应用程序。假设我们所工作的公司通过网络销售分类产品。这些产品分为以下几个类别:

类别ID类别名称
1
饮料
2
调味品
3
糖果
4
奶制品
...
...
假设我们已创建了名为ListProductsByCategory.aspx的ASP.NET网页,该网页在查询字符串中接受类别ID值,并显示属于该类的所有产品。因此,要查看我们销售的饮料的用户可以访问ListProductsByCategory.aspx?CategoryID=1,而那些要查看奶制品的用户可以访问ListProductsByCategory.aspx?CategoryID=4。此外,还假设我们有一个名为ListCategories.aspx的页面,该页面列出了待售的所有产品类别。

很显然,这是一个URL重写事例,因为提供给用户的URL没有为用户带来任何意义,也没有为他们提供任何“可删节性”。因此,让我们使用URL重写,以便在用户访问/Products/Beverages.aspx时,他们的URL将被重写为ListProductsByCategory.aspx?CategoryID=1。我们可以在Web.config文件中使用以下URL重写规则来实现此功能。

~/Products/Beverages/.aspx~/ListProductsByCategory.aspx?CategoryID=1

正如您可以看到的,此规则将进行搜索,以查看用户请求的路径是否为/Products/Beverages.aspx。如果是,它便将URL重写为/ListProductsByCategory.aspx?CategoryID=1。

注意:请注意,<LookFor>元素对Beverages.aspx中的句点进行了转义。这是因为在正则表达式模式中使用了<LookFor>值,并且句点是正则表达式中的特殊字符,该字符表示“匹配任意字符”,例如,与URL/Products/BeveragesQaspx匹配。通过转义句点(使用/.),可以表明我们要匹配的是文字句点,而不是任何旧的字符。

有了此规则之后,当用户访问/Products/Beverages.aspx时,页面上将显示待售的饮料。图3显示了访问/Products/Beverages.aspx的浏览器的快照。请注意,在浏览器的地址栏中,URL将读取/Products/Beverages.aspx,但用户实际看到的是ListProductsByCategory.aspx?CategoryID=1的内容。(实际上,Web服务器上根本不存在/Products/Beverages.aspx文件!)



图3.重写URL之后请求类别

与/Products/Beverages.aspx相似,下面我们要为其他产品类别添加重写规则。此操作仅包括在Web.config文件的<Rules>元素内添加附加的<RewriterRule>元素。请参阅下载内容中的Web.config文件,以获取用于此演示的一组完整的重写规则。

为了使URL更具可删节性,最好使用户只需从/Products/Beverages.aspx中删除Beverages.aspx即可看到产品类别的列表。乍一看,这可能是一项很普通的任务(只需添加一个将/Products/映射到/ListCategories.aspx的重写规则即可)。但此操作存在一个微妙之处,即您必须首先创建一个/Products/目录,并在/Products/目录中添加一个空的Default.aspx文件。

要理解需要执行这些额外步骤的原因,可以参考前面的内容,即URL重写引擎位于ASP.NET级别上。也就是说,如果ASP.NET引擎永远没有机会处理请求,URL重写引擎就没有办法检测传入的URL。而且,请记住,仅当被请求的文件具有相应的扩展名时,IIS才会将传入请求传递给ASP.NET引擎。因此,如果用户访问/Products/,而IIS没有看到任何文件扩展名,那么它将检查目录,以查看是否存在这样一个文件,即该文件名为默认文件名中的一个。(Default.aspx、Default.htm、Default.asp等等。“IIS管理”对话框中“Web服务器属性”对话框的“文档”选项卡对这些默认文件名进行了定义。)当然,如果/Products/目录不存在,IIS将返回HTTP404错误。

因此,我们需要创建/Products/目录。另外,我们还需要在此目录中创建一个文件Default.aspx。这样,当用户访问/Products/时,IIS将检测目录,查看是否存在一个名为Default.aspx的文件,然后将处理过程传递给ASP.NET引擎。然后,URL重写器将在重写URL时分解。

创建目录和Default.aspx文件后,请继续操作,并向<Rules>元素中添加以下重写规则:

~/Products/Default/.aspx~/ListCategories.aspx

有了此规则之后,当用户访问/Products/或/Products/Default.aspx时,他们将看到产品类别列表,如图4所示。



4.向URL添加“可删节性”

处理回发

如果要重写的URL中包含一个服务器端的Web窗体并执行回发,则窗体回发后,将使用带下划线的URL。也就是说,如果用户在浏览器中输入/Products/Beverages.aspx,他们在浏览器地址栏中看到的将是/Products/Beverages.aspx,但是他们看到的内容将是ListProductsByCategory.aspx?CategoryID=1的内容。如果ListProductsByCategory.aspx执行了回发,用户将被回发到ListProductsByCategory.aspx?CategoryID=1,而不是/Products/Beverages.aspx。这样不会中断任何内容,但从用户的角度考虑,如果单击按钮时突然看到URL更改会使他们感到不安。

出现这种情况的原因是:在呈现Web窗体时,它会将其操作属性直接设置为Request对象中文件路径的值。当然,在呈现Web窗体时,URL已从/Products/Beverages.aspx重写为ListProductsByCategory.aspx?CategoryID=1,这表明Request对象报告用户要访问ListProductsByCategory.aspx?CategoryID=1。只需使服务器端窗体不呈现操作属性即可解决此问题。(默认情况下,如果窗体不包含操作属性,浏览器将会回发。)

不幸的是,Web窗体不允许您明确指定操作属性,也不允许您设置某些属性以禁用操作属性的呈现。因此,我们必须自己来扩展System.Web.HtmlControls.HtmlForm类,覆盖RenderAttribute()方法并明确指出它不会呈现操作属性。

由于继承功能,我们可以获得HtmlForm类的所有功能,并且只需添加几行代码即可获得所需的行为。以下显示了自定义类的完整代码:

namespaceActionlessForm{publicclassForm:System.Web.UI.HtmlControls.HtmlForm{protectedoverridevoidRenderAttributes(HtmlTextWriterwriter){writer.WriteAttribute("name",this.Name);base.Attributes.Remove("name");writer.WriteAttribute("method",this.Method);base.Attributes.Remove("method");this.Attributes.Render(writer);base.Attributes.Remove("action");if(base.ID!=null)writer.WriteAttribute("id",base.ClientID);}}}

已被覆盖的RenderAttributes()方法的代码仅包含HtmlForm类的RenderAttributes()方法的准确代码,而不设置操作属性。(我使用LutzRoeder的Reflector来查看HtmlForm类的源代码。)

创建此类并对其进行编译之后,要在ASP.NETWeb应用程序中使用它,应首先将其添加到Web应用程序的References文件夹中。然后,要使用它来代替HtmlForm类,只需在ASP.NET网页的顶部添加以下内容即可:


然后,将<formrunat="server">(如果有)替换为:

标记替换为:


并将右边的</form>标记替换为:


您可以在ListProductsByCategory.aspx(包含在本文的下载代码中)中发现操作中的此自定义WebForm类。下载内容中还包含了用于无操作WebForm的VisualStudio.NET项目。

注意:如果要重写的目标URL没有执行回发,则无需使用此自定义WebForm类。



返回页首

创建真正“可删节”的URL

前一部分中介绍的简单URL重写显示了如何轻松地为URL重写引擎配置新的重写规则。但在使用正则表达式时,重写规则的真正功能才会发挥更大作用,本部分将对此进行探讨。

Blog在当今正变得越来越流行,似乎每个人都拥有自己的blog。如果您不熟悉blog:blog是经常更新的个人页面,通常作为联机期刊。大多数blog只记录每天发生的事情,还有一些blog可能关注于特定的主题(例如,电影回顾、体育团队或计算机技术)。

可以在任何地点对blog进行更新,更新频率为从每天几次到每周一次或两次,具体情况取决于作者。通常,blog主页将显示最近的10个条目,但实际上,所有blog软件均提供存档,访问者可以通过存档读取较早的帖子。Blog是用于“可删节”URL的一个功能强大的应用程序。假设在搜索blog的存档时,您在URL/2004/02/14.aspx上发现了您自己。如果您发现自己在阅读2004年2月14日的帖子,您是否觉得很惊讶?而且,您可能希望查看2004年2月的所有帖子,在这种情况下,您可以尝试将URL删节为/2004/02/。要查看2004年的所有帖子,您可以尝试访问/2004/。

维护blog时,最好为访问者提供此级别的URL“可删节性”。许多blog引擎都提供此功能,但我们将讨论如何使用URL重写来实现此功能。

首先,我们需要一个ASP.NET网页,此页面将按照日、月或年来显示blog条目。假设我们有一个ShowBlogContent.aspx页面,该页面的查询字符串参数为年、月和日。要查看2004年2月14日的帖子,我们可以访问ShowBlogContent.aspx?year=2004&month=2&day=14。要查看2004年2月的所有帖子,我们可以访问ShowBlogContent.aspx?year=2004&month=2。最后,要查看2004年的所有帖子,我们可以浏览到ShowBlogContent.aspx?year=2004。(可以在本文的下载内容中找到ShowBlogContent.aspx的代码。)

在这种情况下,如果用户访问/2004/02/14.aspx,我们需要将URL重写为ShowBlogContent.aspx?year=2004&month=2&day=14。所有三种情况(URL指定了年、月和日时;URL仅指定了年和月时;URL仅指定了年时)均可使用重写规则进行处理:

~/(/d{4})/(/d{2})/(/d{2})/.aspx~/ShowBlogContent.aspx?year=$1&month=$2&day=$3~/(/d{4})/(/d{2})/Default/.aspx~/(/d{4})/Default/.aspx~/ShowBlogContent.aspx?year=$1

这些重写规则表明了正则表达式的功能。在第一个规则中,我们使用模式(/d{4})/(/d{2})/(/d{2})/.aspx查找URL。在简明英语中,它对应了这样一个字符串:首先是四个数字,后跟一个斜杠,然后是两个数字,后跟一个斜杠,然后再跟两个数字,最后是一个.aspx。每个数字组周围的括号非常重要,通过它可以在相应的<SendTo>属性中引用这些括号内的匹配字符。特别是,我们可以针对第一、第二和第三个括号组分别使用$1、$2和$3引用回括号内的匹配组。

注意:由于Web.config文件采用XML格式,但是必须对元素文字部分中的字符(如&、<和>)进行转义。在第一个规则的<SendTo>元素中,&被转义为&。在第二个规则的<SendTo>中使用了另外一种技术(使用<![CDATA[...]]>元素),无需对内部的内容进行转义。可以使用两种方法中的任何一种,并且都会得到相同的结果。

图5、6和7显示了操作中的URL重写。数据实际上是从我的bloghttp://scottonwriting.net/中拖过来的。图5中显示了2003年11月7日的帖子;图6中显示了2003年11月的所有帖子;图7显示了2003年的所有帖子。



5.2003年11月7日的帖子



6.2003年11月的所有帖子



7.2003年的所有帖子

注意:URL重写引擎在<LookFor>元素中需要使用正则表达式模式。如果您对正则表达式不熟悉,可以阅读我在早些时候编写的一篇文章AnIntroductiontoRegularExpressions。另外,还有一个很好的网站:RegExLib.com,在那里您可以获取有关常用正则表达式的帮助信息,还可以共享您自己的自定义正则表达式。

构建必备的目录结构

当请求/2004/03/19.aspx时,IIS将通知.aspx扩展,并将请求路由到ASP.NET引擎。请求在ASP.NET引擎的管道中移动时,URL将被重写为ShowBlogContent.aspx?year=2004&month=03&day=19,并且访问者会看到2004年3月19日的blog条目。但是当用户浏览到/2004/03/时将会发生什么情况呢?除非有一个/2004/03/目录,否则IIS将返回一个404错误。此外,此目录中还需要具有Default.aspx页面,以便可以将请求传递给ASP.NET引擎。

因此,要使用这种方法,必须手动创建一个用于每年的目录(其中包含blog条目),并且目录中具有一个Default.aspx页面。另外,在每年目录中,您需要再手动创建十二个目录(01、02、?、?...、12),并且每个目录中均有一个Default.aspx文件。(如上所述,我们还必须执行前面演示中的操作,即在/Products/目录中添加一个Default.aspx文件,以便访问/Products/时可以正确显示ListCategories.aspx。)

很显然,添加这样一个目录结构可能是一件很痛苦的事情。解决此问题的方法是使所有传入的IIS请求都映射到ASP.NET引擎。通过这种方法,即使访问URL/2004/03/,IIS也会如实地将请求传递给ASP.NET引擎(即使并不存在/2004/03/目录)。但是,使用这种方法将使ASP.NET引擎负责处理到达Web服务器的所有类型的传入请求,包括图像、CSS文件、外部JavaScript文件、MacromediaFlash文件,等等。

对处理所有文件类型的全面讨论远远超出了本文的范围。有关使用此技术的ASP.NETWeb应用程序的示例,请参阅.Text,一个开放源blog引擎。.Text可以配置为将所有请求均映射到ASP.NET引擎。它可以使用自定义HTTP处理程序来处理生成所有文件类型的问题,自定义HTTP处理程序了解如何生成典型的静态文件类型(图像、CSS文件,等等)。



返回页首

结论

在本文中,我们讨论了如何在ASP.NET级别通过HttpContext类的RewriteUrl()方法来执行URL重写。正如我们所看到的,RewriteUrl()更新了特定的HttpContext'sRequest属性,从而更新了被请求的文件和路径。最终结果是,从用户角度来看,他们要访问某个特定的URL,但从Web服务器端来看,被请求的却是另一个URL。

可以在HTTP模块或HTTP处理程序中重写URL。在本文中,我们介绍了如何使用HTTP模块执行重写,并讨论了在管道中的不同阶段执行重写的结果。

当然,如果执行ASP.NET级别的重写,则仅当已成功地将请求从IIS传递给ASP.NET引擎后才会发生URL重写。实际上,只有用户请求带.aspx扩展名的页面时才会出现这种情况。但是,如果您要使用户可以进入实际并不存在的URL,但又希望重写到现有的ASP.NET页面,则必须创建虚拟目录和Default.aspx页面,或者对IIS进行配置,以使所有传入请求一律被路由到ASP.NET引擎。

overred/d123.aspx(注意:开头必须为d
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: