构建 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>要查找的模式</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() 事件中,它将HttpApplication 的 AuthorizeRequest 事件绑定到 BaseModuleRewriter_AuthorizeRequest 方法。BaseModuleRewriter_AuthorizeRequest方法将调用该类传入被请求的 Path 的 Rewrite() 方法,以及传入 Init() 方法的 HttpApplication 对象。Rewrite() 方法是抽象的,也就是说,在BaseModuleRewriter 类中,Rewrite() 方法没有方法主体;从 BaseModuleRewriter 派生而来的类必须覆盖此方法并提供方法主体。
具有此基类后,只需创建由 BaseModuleRewriter 派生的类即可,该类可以覆盖 Rewrite() 并在那里执行 URL 重写逻辑。下面显示了BaseModuleRewriter 的代码。
public abstract class BaseModuleRewriter : IHttpModule { public virtual void Init(HttpApplication app) { // 警告!此代码不适用于 Windows 身份验证! // 如果使用 Windows 身份验证, // 请改为 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 身份验证与文件授权结合使用,您需要对此做出更改,以便可以在 BeginRequest 或 AuthenticateRequest 事件中执行 URL 重写。
ModuleRewriter 类扩展了 BaseModuleRewriter 类,并负责执行实际的 URL 重写。ModuleRewriter 包含单一覆盖方法(Rewrite()),如下所示:
protected override void Rewrite(string requestedPath, System.Web.HttpApplication app) { // 获得配置规则 RewriterRuleCollection rules = RewriterConfiguration.GetConfig().Rules; // 遍历每个规则... for(int i = 0; i < rules.Count; i++) { // 获得要查找的模式,并且 // 解析 Url(转换为相应的目录) string lookFor = "^" + RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, rules[i].LookFor) + "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); } } }quot;; // 创建 regex(请注意,已设置 IgnoreCase...) Regex re = new Regex(lookFor, RegexOptions.IgnoreCase); // 查看是否找到了匹配的规则 if (re.IsMatch(requestedPath)) { // 找到了匹配的规则 -- 进行必要的替换 string sendToUrl = RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, re.Replace(requestedPath, rules[i].SendTo)); // 重写 URL RewriterUtils.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.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> <!-- 产品制表者规则 --> <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 将返回 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 所示。
图 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 类的所有功能,并且只需添加几行代码即可获得所需的行为。以下显示了自定义类的完整代码:
___FCKpd___6
已被覆盖的 RenderAttributes() 方法的代码仅包含 HtmlForm 类的 RenderAttributes() 方法的准确代码,而不设置操作属性。(我使用 Lutz Roeder 的 Reflector 来查看 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 Form 的 Visual Studio .NET 项目。
注意:如果要重写的目标 URL 没有执行回发,则无需使用此自定义 Web Form 类。
创建真正“可删节”的 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 重写。数据实际上是从我的 blog http://scottonwriting.net/ 中拖过来的。图 5 中显示了 2003 年 11 月 7 日的帖子;图 6 中显示了 2003 年 11 月的所有帖子;图 7 显示了 2003 年的所有帖子。
图 5. 2003 年 11 月 7 日的帖子
图 6. 2003 年 11 月的所有帖子
图 7. 2003 年的所有帖子
注意:URL 重写引擎在 <LookFor> 元素中需要使用正则表达式模式。如果您对正则表达式不熟悉,可以阅读我在早些时候编写的一篇文章 An Introduction to Regular Expressions。另外,还有一个很好的网站: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 文件、Macromedia Flash 文件,等等。
对处理所有文件类型的全面讨论远远超出了本文的范围。有关使用此技术的 ASP.NET Web 应用程序的示例,请参阅 .Text,一个开放源 blog 引擎。.Text可以配置为将所有请求均映射到 ASP.NET 引擎。它可以使用自定义 HTTP 处理程序来处理生成所有文件类型的问题,自定义 HTTP 处理程序了解如何生成典型的静态文件类型(图像、CSS 文件,等等)。
结论
在本文中,我们讨论了如何在 ASP.NET 级别通过 HttpContext 类的 RewriteUrl() 方法来执行 URL 重写。正如我们所看到的,RewriteUrl() 更新了特定的 HttpContext's Request 属性,从而更新了被请求的文件和路径。最终结果是,从用户角度来看,他们要访问某个特定的 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, and Code
Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team
Essential ASP.NET with Examples in C#
参考资料
URL 重写是涉及到 ASP.NET 和竞争服务器端 Web 技术的一个主题。例如,Apache Web 服务器提供了名为 mod_rewrite 的 URL 重写模块。Mod_rewrite 是一个功能强大的重写引擎,提供了基于条件(如 HTTP 标题和服务器变量)的重写规则以及使用正则表达式的重写规则。有关 mod_rewrite 的详细信息,请查阅 A User's Guide to URL Rewriting with the Apache Web Server。
还有许多有关使用 ASP.NET 执行 URL 重写的文章。Rewrite.NET - A URL Rewriting Engine for .NET 对创建模拟 mod_rewrite 正则表达式规则的 URL 重写引擎进行了介绍。URL Rewriting With ASP.NET 为 ASP.NET 的 URL 重写功能提供了很好的概述。Ian Griffiths 包含一个 blog entry,介绍了有关使用 ASP.NET 进行 URL 重写的一些注意事项(如在本文中讨论过的回发问题)。Fabrice Marguerie (read more) 和 Jason Salas (read more) 具有有关使用 URL 重写来增强搜索引擎定位功能的 blog 条目。