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,方法是将 type=summary 替换为http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099\&类似 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 网站通常会生成一个网页,该网页基于 querystring 参数显示数据库数据的子集。 例如,在设计电子商务网站时,你的任务之一是允许用户浏览要销售的产品。 为此,可以创建一个名为 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 不符合 Neilsen 的标准,也不易于记住。 要求用户键入 querystring 值会使 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 日的文章,请访问类似于 的 http://someblog.com/2004/01/28URL。 如果 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,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 如何管理传入请求的深入讨论有点超出了本文的讨论范围。 不过,在 Michele Leroux Bustamante 的文章《 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,然后 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 处理程序涉及将几行 XML 添加到应用程序的Web.config文件。

具体而言,若要将 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>

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

注意 注册 HTTP 处理程序时,请务必确保在 IIS 中将 HTTP 处理程序使用的扩展映射到 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 重写

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

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

在 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 模块时重写。 使用表单身份验证时,会出现此方法的缺点。 如果以前使用过表单身份验证,你知道当用户访问受限资源时,他们会自动重定向到指定的登录页。 成功登录后,用户将首先被发送回他们尝试访问的页面。

如果在 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 级文件;如果此类用户尝试访问 /info/employee.aspx?empID=1001,则他们将收到授权错误。 但是,如果将 URL 重写移动到 AuthenticateRequest 事件,当 FileAuthorizationModule 检查安全设置时,它仍然认为请求的文件是 /people/ScottMitchell.aspx,因为 URL 尚未重写。 因此,文件授权检查将传递,允许此用户查看重写的 URL /info/employee.aspx?empID=1001 的内容。

那么,何时应在 HTTP 模块中执行 URL 重写? 这取决于你使用的身份验证类型。 如果不使用任何身份验证,则 URL 重写是否发生在 BeginRequestAuthenticateRequestAuthorizeRequest 中并不重要。 如果使用的是 forms 身份验证,并且未使用 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 处理程序。 (这是内置网页 HTTP 处理程序工厂 PageHandlerFactory 的内置 ASP.NET 技术。)

由于所有 HTTP 模块都将在实例化自定义 HTTP 处理程序工厂之前进行初始化,因此在将 URL 重写置于事件的后一阶段时,使用 HTTP 处理程序工厂会面临相同的挑战,即文件授权将不起作用。 因此,如果依赖于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文件中重写规则的结构。 首先,需要在 Web.config 文件中指示是否要使用 HTTP 模块或 HTTP 处理程序执行 URL 重写。 在下载中,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 () 方法,该方法与传入 Init () 方法的 HttpApplication 对象一起传入请求的路径Rewrite () 方法是抽象的,这意味着在 BaseModuleRewriter 类中,Rewrite () 方法没有方法主体;相反,从 BaseModuleRewriter 派生的类必须重写此方法并提供方法主体。

完成此基类后,我们现在要做的就是创建一个派生自 BaseModuleRewriter 的类,该类重写 Rewrite () 并在此处执行 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 包含一个重写的方法,即 Rewrite () ,如下所示:

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 网页,该网页接受 querystring 中的类别 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/饮料.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/ 目录。 此外,我们需要在此目录中创建单个文件 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 窗体时,它会将其 action 属性显式设置为 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 应用程序的 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 Form 类,此类包含在本文的下载中。 下载中还包括适用于无操作 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,它采用查询字符串参数 year、month 和 day。 若要查看 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 的代码可在本文的 download.)

因此,如果用户访问 /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 的Request 属性,更新正在请求的文件和路径。 净效果是,从用户的角度来看,他们访问的是特定的 URL,但实际上在 Web 服务器端请求了不同的 URL。

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

当然,使用 ASP.NET 级重写时,仅当请求成功从 IIS 传递到 ASP.NET 引擎时,才能进行 URL 重写。 当用户请求具有 .aspx 扩展名的页面时,自然会发生这种情况。 但是,如果希望此人能够输入可能实际上不存在的 URL,但宁愿重写到现有的 ASP.NET 页面,则必须创建 mock 目录和 Default.aspx 页面,或配置 IIS,以便所有传入请求都盲目路由到 ASP.NET 引擎。

ASP.NET:提示、教程和代码

Microsoft ASP.NET 团队的 Microsoft ASP.NET 编码策略

包含 C 中示例的基本 ASP.NET#

咨询的工程

URL 重写是一个主题,ASP.NET 和竞争的服务器端 Web 技术备受关注。 例如,Apache Web 服务器提供了一个名为 mod_rewrite 的 URL 重写模块。 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 重写功能。 Ian Griffiths 有一 篇博客文章 ,内容涉及与 ASP.NET URL 重写相关的一些注意事项,例如本文中讨论的回发问题。 Fabrice Marguerie (阅读更多) 和 Jason Salas (阅读更多) 博客全文使用 URL 重写来提升搜索引擎的位置。

 

关于作者

Scott Mitchell 是五本书的作者,4GuysFromRolla.com 的创始人,在过去的五年里一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 可以在 上或通过他的博客联系 mitchell@4guysfromrolla.com 他,该博客可在 中找到 http://ScottOnWriting.NET

© Microsoft Corporation. 保留所有权利。