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。 是否找到 http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099\&等 URL;type=summary? 或者,你可能有一堆网页已从一个目录或网站移动到另一个目录或网站,导致已为旧 URL 添加书签的访问者链接断开。 在本文中,我们将探讨如何使用 URL 重写,通过替换 http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099\&来将那些丑陋的 URL 缩短为有意义的、难忘的 URL;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 参数指定。 也就是说,如果用户想要浏览小组件以供销售,并且所有小组件都有类别 ID 为 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 请求的非托管编译类。其任务是为请求的资源生成内容。

例如,如果请求传入名为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 相关的extensions—.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。 可以使用 ASP.NET 重写 URL,因为 ASP.NET 引擎与 IIS 非常相似。 出现相似之处是因为 ASP.NET 引擎:

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

与 IIS 一样,在请求生存期内,ASP.NET 引擎触发事件,指示其从一种处理状态更改为另一种状态。 例如,当 ASP.NET 引擎首次响应请求时,将触发 BeginRequest 事件。 AuthenticateRequest 事件随后触发,该事件在用户标识已建立时发生。 (还有其他许多事件: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的配置/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 示例中,如果 .scott 扩展未在 IIS 中映射到 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(string) 方法更新 请求 对象的 PathQueryString 属性。 除了 RewritePath(string),.NET Framework 版本 1.1 还包括另一种形式的 RewritePath() 方法,该方法接受三个字符串输入参数。 此备用重载形式不仅设置 Request 对象的 PathQueryString 属性,还设置内部成员变量,这些变量用于为其 PhysicalPathPathInfoFilePath 属性计算 Request 对象的值。

若要在 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 模块使用 请求 对象的属性来执行其职责。 (回想一下,重写路径会更改 请求 对象的属性值。下面列出了这些德国内置 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 页面的适当访问权限。

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

那么,何时应在 HTTP 模块中执行 URL 重写? 这取决于使用的身份验证类型。 如果不使用任何身份验证,则 URL 重写发生在 BeginRequestAuthenticateRequestAuthorizeRequest中并不重要。 如果使用表单身份验证,而不使用 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 方法调用类的 Rewrite() 方法,该方法传入请求的 Path,以及传递给 Init() 方法的 HttpApplication 对象。 Rewrite() 方法是抽象的,这意味着在 BaseModuleRewriter 类中,Rewrite() 方法没有方法主体;相反,从 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
      }
   }
}

Rewrite() 方法首先从 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 应用程序。 假设我们为在线销售各种产品的公司工作。 这些产品分为以下类别:

类别 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> 元素将转义Beverages.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 破解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 将返回 HTTP 404 错误。

因此,我们需要创建 /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 所示。

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,这意味着 请求 对象报告用户正在访问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 应用程序的 References 文件夹中。 然后,若要代替 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 窗体类在操作中,本文的下载内容包括此类。 下载中还包括适用于无操作 Web 窗体的 Visual Studio .NET 项目。

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

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

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

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

根据作者的不同,博客每天更新几次,每周或两次。 通常,博客主页显示最近 10 个条目,但几乎所有博客软件都提供了一个存档,访问者可以通过该存档来阅读较旧的帖子。 博客是“可黑客攻击”URL 的绝佳应用程序。 想象一下,在搜索博客的存档时,你发现自己在 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&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>
      <!-- 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>

这些重写规则演示正则表达式的强大功能。 在第一个规则中,我们查找采用模式的 URL(\d{4})/(\d{2})/(\d{2})\.aspx。 在纯英语中,这与一个字符串匹配一个字符串,其中包含四个数字,后跟一个正斜杠,后跟两个数字,后跟一个正斜杠,后跟两个数字,后跟.aspx。 每个数字分组周围的括号至关重要-它允许我们引用相应 <SendTo> 属性中这些括号内的匹配字符。 具体而言,我们可以分别使用 $1、$2 和 $3 引用第一个、第二个和第三个括号分组的匹配括号分组。

注释 由于 Web.config 文件是 XML 格式的,因此必须转义元素的文本部分中的字符,如 &、<和 >。 在第一个规则的 <SendTo> 元素中,& 转义为 &。 在第二个规则的 <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 引擎。 它可以使用知道如何提供典型静态文件类型(图像、CSS 文件等)的自定义 HTTP 处理程序来处理为所有文件类型提供服务。

结论

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

可以在 HTTP 模块或 HTTP 处理程序中重写 URL。 在本文中,我们研究了如何使用 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# 中的示例 Essential 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 重写引擎。 URL 重写 with ASP.NET 还提供了 ASP 的良好概述。NET 的 URL 重写功能。 伊恩·格里菲斯博客文章 了与 URL 重写 ASP.NET 相关的一些注意事项,如本文中讨论的回发问题。 Fabrice Marguerie阅读更多)和 Jason Salas阅读更多)都有博客全文,使用 URL 重写来提高搜索引擎位置。

 

关于作者

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

© Microsoft公司。 保留所有权利。