ASP.NET 中的 URL 重写

 

斯科特·米切尔
4GuysFromRolla.com

2004 年 3 月

适用于:
   Microsoft® ASP.NET

总结:检查如何使用 Microsoft ASP.NET 执行动态 URL 重写。 URL 重写是截获传入 Web 请求并自动将其重定向到其他 URL 的过程。 讨论实现 URL 重写的各种技术,并检查 URL 重写的实际方案。 (31 个打印页面)

下载本文的源代码

目录

简介
URL 重写的常见用途
当请求到达 IIS 时会发生什么情况
实现 URL 重写
生成 URL 重写引擎
使用 URL 重写引擎执行简单 URL 重写
创建真正“黑客攻击”URL
结束语
相关书籍

简介

花点时间查看网站上的一些 URL。 是否找到 type=summary 等http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099\& URL? 或者,你可能有一堆网页从一个目录或网站移动到另一个目录或网站,从而导致已为已将旧 URL 添加书签的访问者链接断开。 在本文中,我们将介绍如何使用 URL 重写 来将那些丑陋的 URL 缩短为有意义的、难忘的 URL,方法是将 http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099\&type=summary 替换为类似 http://yoursite.com/people/sales/chuck.smith内容。 我们还将了解如何使用 URL 重写来创建智能 404 错误。

URL 重写是截获传入 Web 请求并将请求重定向到其他资源的过程。 执行 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 的页面,用于显示给定类别的产品。 要查看的类别的产品将由 querystring 参数指定。 也就是说,如果用户想要浏览要销售的小组件,并且所有小组件都有一个 CategoryID 为 5,则用户将访问: http://yousite.com/displayCategory.aspx?CategoryID=5

使用此类 URL 创建网站有两个缺点。 首先,从最终用户的角度来看,URL http://yousite.com/displayCategory.aspx?CategoryID=5 是一团糟。 可用性专家 Jakob Neilsen建议 选择 URL,以便:

  • 短。
  • 易于键入。
  • 可视化网站结构。
  • “Hackable”,允许用户通过黑客攻击 URL 的某些部分来浏览网站。

我会添加到该列表中,URL 也应该易于记住。 URL http://yousite.com/displayCategory.aspx?CategoryID=5 不满足尼尔森的标准,也不容易记住。 要求用户键入查询字符串值会使 URL 难以键入,并且仅让了解查询字符串参数的用途及其名称/值配对结构的经验丰富的 Web 开发人员才能将 URL“黑客攻击”。

更好的方法是允许一个明智的、难忘的 URL,例如 http://yoursite.com/products/Widgets。 只需查看 URL 即可推断将显示的内容 - 有关小组件的信息。 URL 也易于记住和共享。 我可以告诉我的同事,“签出 yoursite.com/products/Widgets”,她很可能能够打开页面,而无需再次问我 URL 是什么。 (尝试这样做,例如,Amazon.com 页面!) URL 也会出现,并且应该行为“可黑客”。也就是说,如果用户黑客攻击 URL 的末尾,并键入 http://yoursite.com/products,他们应看到 所有 产品的列表,或至少列出他们可以查看的所有类别的产品。

注意 对于“可黑客攻击”URL 的主要示例,请考虑许多博客引擎生成的 URL。 若要查看 2004 年 1 月 28 日的文章,一个访问 URL,如下所示 http://someblog.com/2004/01/28。 如果 URL 被黑客攻击, http://someblog.com/2004/01用户将看到 2004 年 1 月的所有帖子。 进一步削减,以显示 http://someblog.com/2004 2004年的所有帖子。

除了简化 URL 外,URL 重写还通常用于处理网站重组,否则会导致大量链接和过时书签损坏。

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

在仔细研究如何实现 URL 重写之前,请务必了解 Microsoft® Internet Information Services (IIS) 如何处理传入请求。 当请求到达 IIS Web 服务器时,IIS 会检查请求的文件扩展名,以确定如何处理请求。 可以通过 IIS 以本机方式处理请求(HTML 页面、图像和其他静态内容),或者 IIS 可以将请求路由到 ISAPI 扩展。 (ISAPI 扩展是处理传入 Web 请求的非托管编译类。其任务是生成所请求 resource.) 的内容

例如,如果请求传入名为 Info.asp 的网页,IIS 会将消息路由到 asp.dll ISAPI 扩展。 然后,此 ISAPI 扩展将加载请求的 ASP 页,执行它,并将其呈现的 HTML 返回到 IIS,然后将它发送回请求客户端。 对于 ASP.NET 页,IIS 会将消息路由到 aspnet_isapi.dll ISAPI 扩展。 aspnet_isapi.dll ISAPI 扩展随后将处理移交给托管 ASP.NET 工作进程,该进程处理请求,并返回 ASP.NET 网页呈现的 HTML。

可以自定义 IIS 以指定映射到哪些 ISAPI 扩展的扩展。 图 1 显示了Internet Information Services管理工具中的“应用程序配置”对话框。 请注意,ASP.NET 相关的扩展(.aspx、.ascx、.config、.asmx、.rem、.cs、.vb 等)都映射到aspnet_isapi.dll ISAPI 扩展。

ms972974.urlrewriting_fig01(en-us,MSDN.10).gif

图 1. 为文件扩展名配置映射

全面讨论 IIS 如何管理传入请求,远远超出了本文的范围。 不过,可以在米歇尔·勒鲁克斯·布斯塔曼特的 IIS 和 ASP.NET 文章中找到一个伟大的深入讨论。 请务必了解,ASP.NET 引擎仅掌握其扩展显式映射到 IIS 中的aspnet_isapi.dll的传入 Web 请求。

使用 ISAPI 筛选器检查请求

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

与 ISAPI 扩展一样,ISAPI 筛选器是 Web 服务器上安装的非托管代码块。 ISAPI 扩展旨在为对特定文件类型的请求生成响应。 另一方面,ISAPI 筛选器包含用于响应 IIS 引发的事件的代码。 ISAPI 筛选器可以截获甚至修改传入和传出数据。 ISAPI 筛选器有许多应用程序,包括:

  • 身份验证和授权。
  • 日志记录和监视。
  • HTTP 压缩。
  • URL 重写。

虽然 ISAPI 筛选器可用于执行 URL 重写,但本文介绍如何使用 ASP.NET 实现 URL 重写。 但是,我们将讨论实现 URL 重写作为 ISAPI 筛选器与使用 ASP.NET 中可用的技术之间的权衡。

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

在 ASP.NET 之前,需要使用 ISAPI 筛选器在 IIS Web 服务器上重写 URL。 URL 重写可以通过 ASP.NET 实现,因为 ASP.NET 引擎与 IIS 非常相似。 出现相似之处是因为 ASP.NET 引擎:

  1. 在处理请求时引发事件。
  2. 允许任意数量的 HTTP 模块 处理引发的事件,类似于 IIS 的 ISAPI 筛选器。
  3. 委托将请求的资源呈现到 HTTP 处理程序,这类似于 IIS 的 ISAPI 扩展。

与 IIS 一样,在请求的生存期内,ASP.NET 引擎触发事件,指示其从一种处理状态更改为另一种状态。 例如,当 ASP.NET 引擎首次响应请求时,将触发 BeginRequest 事件。 接下来会触发 AuthenticationRequest 事件,该事件在用户标识已建立时发生。 (还有其他许多事件-AuthorizeRequestResolveRequestCacheEndRequest 等。这些事件是 System.Web.HttpApplication 类的事件;有关详细信息,请参阅 HttpApplication 类概述 技术文档.)

如上一部分所述,可以创建 ISAPI 筛选器以响应 IIS 引发的事件。 同样,ASP.NET 提供了 HTTP 模块 ,这些模块可以响应 ASP.NET 引擎引发的事件。 可以将 ASP.NET Web 应用程序配置为具有多个 HTTP 模块。 对于由 ASP.NET 引擎处理的每个请求,将初始化每个配置的 HTTP 模块,并允许将事件处理程序连接到在处理请求期间引发的事件。 意识到每个请求中都使用了许多内置 HTTP 模块。 内置 HTTP 模块之一是 FormsAuthenticationModule,它首先检查是否正在使用表单身份验证,如果是,则是否对用户进行身份验证。 否则,用户会自动重定向到指定的登录页。

回想一下,对于 IIS,传入请求最终定向到 ISAPI 扩展,其作业是返回特定请求的数据。 例如,当经典 ASP 网页的请求到达时,IIS 会将请求移交给 asp.dll ISAPI 扩展,其任务是返回所请求的 ASP 页的 HTML 标记。 ASP.NET 引擎使用类似的方法。 初始化 HTTP 模块后,ASP.NET 引擎的下一个任务是确定应处理请求的 HTTP 处理程序

通过 ASP.NET 引擎传递的所有请求最终都到达 HTTP 处理程序或 HTTP 处理程序工厂 (HTTP 处理程序工厂,只需返回 HTTP 处理程序的实例,然后用于处理请求) 。 最终的 HTTP 处理程序呈现请求的资源,并返回响应。 此响应将发送回 IIS,然后将它返回到发出请求的用户。

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

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

ms972974.urlrewriting_fig02(en-us,MSDN.10).gif

图 2. IIS 和 ASP.NET 的请求处理

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

创建自定义 HTTP 模块和 HTTP 处理程序是相对简单的任务,其中包括创建实现正确接口的托管类。 HTTP 模块必须实现 System.Web.IHttpModule 接口,而 HTTP 处理程序和 HTTP 处理程序工厂必须分别实现 System.Web.IHttpHandler 接口和 System.Web.IHttpHandlerFactory 接口。 创建 HTTP 处理程序和 HTTP 模块的具体内容超出了本文的范围。 为了获得良好的背景,请阅读 Mansoor Ahmed Siddiqui 的文章、 ASP.NET 中的 HTTP 处理程序和 HTTP 模块

创建自定义 HTTP 模块或 HTTP 处理程序后,必须将其注册到 Web 应用程序。 为整个 Web 服务器注册 HTTP 模块和 HTTP 处理程序只需简单添加machine.config文件;为特定 Web 应用程序注册 HTTP 模块或 HTTP 处理程序涉及向应用程序的Web.config文件添加几行 XML。

具体而言,若要将 HTTP 模块添加到 Web 应用程序,请在Web.config的配置/system.web 部分中添加以下行:

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

类型值提供 HTTP 模块的程序集和类名称,而名称值提供友好名称,可通过该名称在 Global.asax 文件中引用 HTTP 模块。

http 处理程序和 HTTP 处理程序工厂由 <Web.config configuration/system.web 节中的 httpHandlers> 标记配置,如下所示:

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

回想一下,对于每个传入请求,ASP.NET 引擎确定应使用哪个 HTTP 处理程序来呈现请求。 此决定基于传入的请求谓词和路径做出。 谓词指定了哪种类型的 HTTP 请求(GET 或 POST),而路径指定所请求文件的位置和文件名。 因此,如果想要让 HTTP 处理程序处理具有 .scott 扩展名的文件的所有请求(GET 或 POST),我们会将以下内容添加到Web.config文件中:

<httpHandlers>
   <add verb="*" path="*.scott" type="type" />
</httpHandlers>

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

注意 注册 HTTP 处理程序时,请务必确保 HTTP 处理程序使用的扩展在 IIS 中映射到 ASP.NET 引擎。 也就是说,在我们的 .scott 示例中,如果 IIS 中未将 .scott 扩展名映射到 aspnet_isapi.dll ISAPI 扩展,则对文件 foo.scott 的请求将导致 IIS 尝试返回文件 foo.scott 的内容。 为了使 HTTP 处理程序处理此请求,必须将 .scott 扩展映射到 ASP.NET 引擎。 然后,ASP.NET 引擎会将请求正确路由到相应的 HTTP 处理程序。

有关注册 HTTP 模块和 HTTP 处理程序的详细信息,请务必查阅 <httpModules> 元素文档 以及 <httpHandlers> 元素文档

实现 URL 重写

可以使用 IIS Web 服务器级别的 ISAPI 筛选器,或者在 ASP.NET 级别使用 HTTP 模块或 HTTP 处理程序实现 URL 重写。 本文重点介绍如何使用 ASP.NET 实现 URL 重写,因此我们不会深入了解使用 ISAPI 筛选器实现 URL 重写的具体细节。

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

使用 Microsoft® .NET Framework版本 1.0 时,RewritePath () 方法接受单个字符串,这是要使用的新路径。 在内部, HttpContext 类的 RewritePath (字符串) 方法更新 Request 对象的 PathQueryString 属性。 除了 RewritePath (字符串) 外,.NET Framework 版本 1.1 还包括另一种接受三个字符串输入参数的 RewritePath () 方法形式。 此备用重载窗体不仅设置 Request 对象的 PathQueryString 属性,还设置用于计算 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.NET HTTP 模块使用 Request 对象的属性来执行其职责。 (回想一下,重写路径会更改 Request 对象的属性值。) 这些德语内置 HTTP 模块及其绑定的事件如下所示:

HTTP 模块 事件 说明
FormsAuthenticationModule AuthenticateRequest 确定是否使用表单身份验证对用户进行身份验证。 否则,用户会自动重定向到指定的登录页。
FileAuthorizationMoudle AuthorizeRequest 使用Windows 身份验证时,此 HTTP 模块会检查以确保 Microsoft® Windows ® 帐户对请求的资源拥有足够的权限。
UrlAuthorizationModule AuthorizeRequest 检查以确保请求者可以访问指定的 URL。 URL 授权是通过Web.config文件中的 <授权> 和 <位置> 元素指定的。

回想一下, BeginRequest 事件在 AuthenticateRequest 之前触发,该事件在 AuthorizeRequest 之前触发。

可以执行 URL 重写的一个安全位置是在 BeginRequest 事件中。 这意味着,如果需要重写 URL,在运行任何内置 HTTP 模块时,该 URL 就会执行此操作。 使用表单身份验证时,会出现此方法的缺点。 如果以前使用表单身份验证,则你知道当用户访问受限资源时,会自动重定向到指定的登录页。 成功登录后,用户将返回他们第一次尝试访问的页面。

如果在 BeginRequestAuthenticateRequest 事件中执行 URL 重写,则提交后,登录页会将用户重定向到重写页面。 也就是说,假设用户键入浏览器窗口 /people/ScottMitchell.aspx,该窗口将重写为 /info/employee.aspx?empID=1001。 如果 Web 应用程序配置为使用表单身份验证,则当用户首次访问 /people/ScottMitchell.aspx 时,首先将 URL 重写为 /info/employee.aspx?empID=1001;接下来, FormsAuthenticationModule 将运行,根据需要将用户重定向到登录页。 但是,成功登录时,用户将发送到的 URL 将是 /info/employee.aspx?empID=1001,因为这是 FormsAuthenticationModule 运行时请求的 URL。

同样,在 BeginRequestAuthenticateRequest 事件中执行重写时, UrlAuthorizationModule 将看到重写的 URL。 这意味着,如果使用 <Web.config文件中的位置> 元素来指定特定 URL 的授权,则必须引用重写的 URL。

若要修复这些细微之处,你可能会决定在 AuthorizeRequest 事件中执行 URL 重写。 虽然此方法修复了 URL 授权并形成身份验证异常,但它引入了新的皱纹:文件授权不再有效。 使用 Windows 身份验证 时,FileAuthorizationModule 会检查以确保经过身份验证的用户具有访问特定 ASP.NET 页面的适当访问权限。

如果一组用户没有对 C:\Inetput\wwwroot\info\employee.aspx 具有Windows级文件访问权限,则Imagine如果此类用户尝试访问 /info/employee.aspx?empID=1001,则他们将收到授权错误。 但是,如果我们将 URL 重写移动到 AuthenticateRequest 事件,当 FileAuthorizationModule 检查安全设置时,它仍然认为请求的文件是 /people/ScottMitchell.aspx,因为 URL 尚未重写。 因此,文件授权检查将通过,允许用户查看重写 URL、/info/employee.aspx?empID=1001 的内容。

那么,何时应在 HTTP 模块中执行 URL 重写? 这取决于你正在使用的身份验证类型。 如果不使用任何身份验证,则 URL 重写发生在 BeginRequest、AuthenticationRequestAuthorizeRequest 中并不重要。 如果使用表单身份验证而不使用Windows 身份验证,请将 URL 重写置于 AuthorizeRequest 事件处理程序中。 最后,如果使用Windows 身份验证,请计划 BeginRequestAuthenticateRequest 事件期间的 URL 重写。

HTTP 处理程序中的 URL 重写

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

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

若要通过 HTTP 处理程序执行 URL 重写,我们可以创建一个 HTTP 处理程序工厂,其 GetHandler () 方法检查请求的路径以确定是否需要重写。 如果这样做,它可以调用传入的 HttpContext 对象的 RewritePath () 方法,如前所述。 最后,HTTP 处理程序工厂可以返回 System.Web.UI.PageParser 类的 GetCompiledPageInstance () 方法返回的 HTTP 处理程序。 (这是内置的 ASP.NET 网页 HTTP 处理程序工厂 PageHandlerFactory 的工作原理。)

由于在实例化自定义 HTTP 处理程序工厂之前,所有 HTTP 模块都将初始化,在使用 HTTP 处理程序工厂时,在将 URL 重写置于事件的后一阶段时会出现相同的挑战,即文件授权将不起作用。 因此,如果你依赖于Windows 身份验证和文件授权,则需要使用 HTTP 模块方法来重写 URL。

在下一部分中,我们将介绍如何生成可重用的 URL 重写引擎。 在检查 URL 重写引擎(本文的代码下载中提供)之后,我们将花费其余两个部分来检查实际使用 URL 重写。 首先,我们将了解如何使用 URL 重写引擎并查看简单的 URL 重写示例。 接下来,我们将利用重写引擎正则表达式功能的强大功能来提供真正“可黑客攻击”的 URL。

生成 URL 重写引擎

为了帮助说明如何在 ASP.NET Web 应用程序中实现 URL 重写,我创建了 URL 重写引擎。 此重写引擎提供以下功能:

  • 使用 URL 重写引擎的 ASP.NET 页面开发人员可以在Web.config文件中指定重写规则。
  • 重写规则可以使用正则表达式来允许强大的重写规则。
  • 可以轻松将 URL 重写配置为使用 HTTP 模块或 HTTP 处理程序。

在本文中,我们将仅使用 HTTP 模块检查 URL 重写。 若要查看 HTTP 处理程序如何用于执行 URL 重写,请参阅本文中可供下载的代码。

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

让我们检查Web.config文件中重写规则的结构。 首先,如果需要使用 HTTP 模块或 HTTP 处理程序执行 URL 重写,则需要在Web.config文件中指示。 在下载中,Web.config文件包含两个已注释掉的条目:

<!--
<httpModules>
   <add type="URLRewriter.ModuleRewriter, URLRewriter" 
        name="ModuleRewriter" />
</httpModules>
-->

<!--
<httpHandlers>
   <add verb="*" path="*.aspx" 
        type="URLRewriter.RewriterFactoryHandler, URLRewriter" />
</httpHandlers>
-->

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

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

<RewriterConfig>
   <Rules>
   <RewriterRule>
      <LookFor>pattern to look for</LookFor>
      <SendTo>string to replace pattern with</SendTo>
   </RewriterRule>
   <RewriterRule>
      <LookFor>pattern to look for</LookFor>
      <SendTo>string to replace pattern with</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方法调用传入请求路径类的重写 () 方法以及传入 Init () 方法的 HttpApplication 对象。 重写 () 方法是抽象的,这意味着在 BaseModuleRewriter 类中,重写 () 方法没有方法主体;相反,从 BaseModuleRewriter 派生的类必须重写此方法并提供方法正文。

有了此基类,我们现在必须做的就是创建一个派生自 BaseModuleRewriter 的类,该类重写 重写 () 并在那里执行 URL 重写逻辑。 BaseModuleRewriter 的代码如下所示。

public abstract class BaseModuleRewriter : IHttpModule
{
   public virtual void Init(HttpApplication app)
   {
      // WARNING!  This does not work with Windows authentication!
      // If you are using Windows authentication, 
      // change to app.BeginRequest
      app.AuthorizeRequest += new 
         EventHandler(this.BaseModuleRewriter_AuthorizeRequest);
   }

   public virtual void Dispose() {}

   protected virtual void BaseModuleRewriter_AuthorizeRequest(
     object sender, EventArgs e)
   {
      HttpApplication app = (HttpApplication) sender;
      Rewrite(app.Request.Path, app);
   }

   protected abstract void Rewrite(string requestedPath, 
     HttpApplication app);
}

请注意, BaseModuleRewriter 类在 AuthorizeRequest 事件中执行 URL 重写。 回想一下,如果使用Windows 身份验证进行文件授权,则需要更改此项,以便在 BeginRequestAuthenticateRequest 事件中执行 URL 重写。

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

protected override void Rewrite(string requestedPath, 
   System.Web.HttpApplication app)
{
   // get the configuration rules
   RewriterRuleCollection rules = 
     RewriterConfiguration.GetConfig().Rules;

   // iterate through each rule...
   for(int i = 0; i < rules.Count; i++)
   {
      // get the pattern to look for, and 
      // Resolve the Url (convert ~ into the appropriate directory)
      string lookFor = "^" + 
        RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, 
        rules[i].LookFor) + "$";

      // Create a regex (note that IgnoreCase is set...)
      Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);

      // See if a match is found
      if (re.IsMatch(requestedPath))
      {
         // match found - do any replacement needed
         string sendToUrl = 
RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, 
            re.Replace(requestedPath, rules[i].SendTo));

         // Rewrite the URL
         RewriterUtils.RewriteUrl(app.Context, sendToUrl);
         break;      // exit the for loop
      }
   }
}

重写 () 方法首先从Web.config文件中获取一组重写规则。 然后,它一次循环访问一个重写规则,对于每个规则,它会获取其 LookFor 属性,并使用正则表达式来确定是否在请求的 URL 中找到匹配项。

如果找到匹配项,则对请求的路径执行正则表达式替换,其值为 SendTo 属性。 然后,此替换的 URL 将传递到 RewriterUtils.RewriteUrl () 方法。 RewriterUtils 是一个帮助程序类,提供 URL 重写 HTTP 模块和 HTTP 处理程序使用的几个静态方法。 RewriterUrl () 方法只是调用 HttpContext 对象的 RewriteUrl () 方法。

注意 你可能已经注意到,执行正则表达式匹配和替换时,将调用 RewriterUtils.ResolveUrl () 。 此帮助程序方法只需将字符串中的任何实例 ~ 替换为应用程序路径的值。

可以使用本文下载 URL 重写引擎的整个代码。 我们检查了最德国的片段,但还有其他组件,例如用于反序列化Web.config文件中 XML 格式的重写规则的类,以及用于 URL 重写的 HTTP 处理程序工厂。 本文的其余三个部分介绍了 URL 重写的实际用法。

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

为了演示操作中的 URL 重写引擎,让我们构建一个利用简单 URL 重写的 ASP.NET Web 应用程序。 Imagine,我们为在线销售各种产品的公司工作。 这些产品分为以下类别:

类别 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>
      <!-- Rules for Product Lister -->
      <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> 元素将转义饮料.aspx 中的句点。 这是因为 <LookFor> 值用于正则表达式模式,句点是正则表达式中的特殊字符,这意味着“匹配任何字符”,这意味着 /Products/BeveragesQaspx 的 URL(例如)匹配。 通过使用 \.) 转义句点 (,指示我们想要匹配文本句点,而不是任何旧字符。

通过此规则,当用户访问 /Products/Beverages.aspx 时,他们将显示要销售的饮料。 图 3 显示了访问 /Products/Beverages.aspx 的浏览器的屏幕截图。 请注意,在浏览器的地址栏中,URL 读取 /Products/Beverages.aspx,但用户实际上正在看到 ListProductsByCategory.aspx 的内容?CategoryID=1。 (事实上,Web 服务器上甚至不存在 /Products/Beverages.aspx 文件!)

ms972974.urlrewriting_fig03(en-us,MSDN.10).gif

图 3. 重写 URL 后请求类别

与 /Products/Beverages.aspx 类似,接下来我们将添加其他产品类别的重写规则。 这只需在Web.config文件中的 Rules> 元素中添加其他 RewriterRule<>元素。< 有关演示的完整重写规则集,请参阅下载中的Web.config文件。

为了使 URL 更具“可黑客攻击性”,如果用户只需从 /Products/Beverages.aspx 中破解饮料.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 将返回 HTTP 404 错误。

因此,我们需要创建 /Products/ 目录。 此外,我们需要在此目录中创建一个默认.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 所示。

ms972974.urlrewriting_fig04(en-us,MSDN.10).gif

图 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 类的所有功能,只需添加少量代码行才能实现所需行为。 自定义类的完整代码如下所示:

namespace ActionlessForm {
  public class Form : System.Web.UI.HtmlControls.HtmlForm
  {
     protected override void RenderAttributes(HtmlTextWriter writer)
     {
        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 () 方法的确切代码,但不设置操作属性。 (我使用 Lutz Roeder 的 反射器 查看 HtmlForm 类的源代码。)

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

<%@ Register TagPrefix="skm" Namespace="ActionlessForm" 
   Assembly="ActionlessForm" %>

然后,在何处 <form runat="server">使用:

<skm:Form id="Form1" method="post" runat="server">

并将结束 </form> 标记替换为:

</skm:Form>

可以在 ListProductsByCategory.aspx 中查看此自定义 Web 窗体类,本文的下载内容包括此类。 下载中还包括一个Visual Studio .NET 项目,用于无操作 Web 窗体。

注意 如果要重写为不执行回发的 URL,则无需使用此自定义 Web 窗体类。

创建真正“可黑客攻击”的 URL

上一部分中演示的简单 URL 重写演示了如何使用新的重写规则轻松配置 URL 重写引擎。 不过,重写规则的真正功能在使用正则表达式时大放异彩,如本部分所示。

这些天,博客越来越受欢迎,似乎 每个人都 有自己的博客。 如果你不熟悉博客,它们通常是更新的个人页面,通常充当在线日记。 大多数博客作者只是写关于他们日常发生的事件,其他人专注于关于特定主题的博客,如电影评论、体育团队或计算机技术。

根据作者的不同,博客每天更新几次,每周或两次。 博客主页通常显示最近 10 个条目,但几乎所有博客软件都提供了一个存档,访问者可以通过该存档读取较旧的帖子。 博客是“可黑客攻击”URL 的绝佳应用程序。 在搜索博客的存档时,Imagine你在 URL /2004/02/14.aspx 中找到自己。 如果你发现自己在2004年2月14日阅读帖子,你会非常惊讶吗? 此外,你可能想要查看 2004 年 2 月的所有帖子,在这种情况下,你可能会尝试将 URL 黑客攻击到 /2004/02/。 若要查看所有 2004 个帖子,可以尝试访问 /2004/。

维护博客时,最好向访问者提供这种级别的 URL“黑客性”。 虽然许多博客引擎都提供此功能,但让我们看看如何使用 URL 重写来实现此功能。

首先,我们需要一个 ASP.NET 网页,该网页将按天、月或年显示博客条目。 假设我们有这样一个页面 ShowBlogContent.aspx,它采用查询字符串参数年、月和日。 若要查看 2004 年 2 月 14 日的帖子,我们可以访问 ShowBlogContent.aspx?year=2004&month=2&day=14。 若要查看 2004 年 2 月的所有帖子,我们将访问 ShowBlogContent.aspx?year=2004&月=2。 最后,若要查看 2004 年的所有帖子,我们将导航到 ShowBlogContent.aspx?year=2004。 (本文的 download.) 中找到 ShowBlogContent.aspx 的代码

因此,如果用户访问 /2004/02/14.aspx,则需要将 URL 重写为 ShowBlogContent.aspx?year=2004&month=2&day=14。 这三种情况 - 当 URL 指定年份、月和日时;当 URL 仅指定年份和月份时;当 URL 仅指定“是”时,可以使用三个重写规则进行处理:

<RewriterConfig>
   <Rules>
      <!-- Rules for Blog Content Displayer -->
      <RewriterRule>
         <LookFor>~/(\d{4})/(\d{2})/(\d{2})\.aspx</LookFor>
         <SendTo>~/ShowBlogContent.aspx?year=$1&amp;month=$2&amp;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> 元素中, & 将转义为 &amp;。 在第二个规则的 <SendTo> 中,通过使用 ![ 使用<替代技术CDATA]。]>元素,不需要转义内部的内容。 任一方法都是可接受的,并且实现相同的结束。

图 5、6 和 7 显示了 URL 重写。 数据实际上是从我的博客拉取的。 http://ScottOnWriting.NET 图 5 显示了 2003 年 11 月 7 日的文章:图 6 显示了 2003 年 11 月的所有帖子:图 7 显示 2003 年的所有帖子。

ms972974.urlrewriting_fig05(en-us,MSDN.10).gif

图 5。 2003 年 11 月 7 日帖子

ms972974.urlrewriting_fig06(en-us,MSDN.10).gif

图 6。 2003 年 11 月的所有帖子

ms972974.urlrewriting_fig07(en-us,MSDN.10).gif

图 7。 2003 年的所有帖子

注意URL 重写引擎需要 LookFor> 元素中的<正则表达式模式。 如果你不熟悉正则表达式,请考虑阅读我前面的文章《正则表达式简介》。 此外,RegExLib.com 一个很好的位置,让你掌握常用正则表达式,以及共享自己制作的正则表达式的存储库。

构建必备目录结构

当 /2004/03/19.aspx 请求传入时,IIS 会记下 .aspx 扩展并将请求路由到 ASP.NET 引擎。 当请求在 ASP.NET 引擎管道中移动时,URL 将重写为 ShowBlogContent.aspx?year=2004 month=03 day=19,访问者将看到 2004&年 3&月 19 日的这些博客条目。 但是当用户导航到 /2004/03/时会发生什么情况? 除非有目录 /2004/03/,否则 IIS 将返回 404 错误。 此外,此目录中需要有 Default.aspx 页,以便将请求移交给 ASP.NET 引擎。

因此,使用此方法,必须手动创建一个目录,其中每年有博客条目,目录中有 Default.aspx 页面。 此外,在每年目录中,需要手动创建 12 个目录(01、02、...、12 个),每个目录都有 Default.aspx 文件。 (回想一下,我们必须在上一个演示中添加具有 Default.aspx 文件的 /Products/ 目录,以便访问 /Products/正确显示 ListCategories.aspx.)

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

对处理所有文件类型的深入讨论远远超出了本文的范围。 不过,对于使用此技术的 ASP.NET Web 应用程序的示例,请查看 。文本,开源博客引擎。 .可以将文本配置为将所有请求映射到 ASP.NET 引擎。 它可以使用一个自定义 HTTP 处理程序来处理所有文件类型,该处理程序知道如何 (图像、CSS 文件等) 提供典型的静态文件类型。

结束语

本文介绍了如何通过 HttpContext 类的 RewriteUrl () 方法在 ASP.NET 级别执行 URL 重写。 如前所述, RewriteUrl () 更新特定的 HttpContext请求 属性,更新请求的文件和路径。 净效果是,从用户的角度来看,他们正在访问特定的 URL,但实际上在 Web 服务器端请求了不同的 URL。

URL 可以在 HTTP 模块或 HTTP 处理程序中重写。 在本文中,我们研究了如何使用 HTTP 模块执行重写,并查看在管道的不同阶段执行重写的后果。

当然,使用 ASP.NET 级重写,只有在成功将请求从 IIS 移交给 ASP.NET 引擎时,才能发生 URL 重写。 当用户请求具有 .aspx 扩展名的页面时,自然会发生此情况。 但是,如果希望该人员能够输入可能不存在的 URL,但宁愿重写到现有的 ASP.NET 页,则必须创建模拟目录和 Default.aspx 页面,或配置 IIS,以便所有传入的请求盲目路由到 ASP.NET 引擎。

ASP.NET:使用技巧、教程和代码

使用 Microsoft ASP.NET 团队Microsoft ASP.NET 编码策略

使用 C 中的示例的基本 ASP.NET#

咨询的工作

URL 重写是一个主题,对于 ASP.NET 和竞争的服务器端 Web 技术,都受到了很多关注。 例如,Apache Web 服务器提供了一个用于 URL 重写的模块,称为 mod_rewrite。 mod_rewrite是一个可靠的重写引擎,基于 HTTP 标头和服务器变量等条件提供重写规则,以及使用正则表达式的重写规则。 有关mod_rewrite的详细信息,请查看使用 Apache Web Server 重写 URL 的用户指南

使用 ASP.NET 重写 URL 时,有许多文章。 Rewrite.NET - 用于 .NET 的 URL 重写引擎 将检查创建模拟mod_rewrite正则表达式规则的 URL 重写引擎。 使用 ASP.NET 重写 URL 也提供了 ASP 的良好概述。NET 的 URL 重写功能。 伊恩·格里菲斯 有一篇 博客文章 ,其中一些注意事项与 URL 重写相关的 ASP.NET,例如本文中讨论的回发问题。 Fabrice Marguerie (阅读更多) 和 Jason Salas (阅读更多) 博客,了解如何使用 URL 重写来提高搜索引擎放置。

 

关于作者

斯科特·米切尔,五本书的作者和 4GuysFromRolla.com 的创始人,在过去五年里一直在与 Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他可以通过他的博客访问 mitchell@4guysfromrolla.com 或通过他的博客,可以在其中 http://ScottOnWriting.NET找到。

© Microsoft Corporation. 保留所有权利。