类,并负责执行实际的 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) + "$";
// 创建 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 类的所有功能,并且只需添加几行代码即可获得所需的行为。以下显示了自定义类的完整代码: 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 的 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 引用回括号内的 上一页 [1] [2] [3] [4] 下一页 |