经常有人请我指导应该如何动态地
“
重写
”URL
,以在他们的
ASP.NETweb
应用中发布比较干净的
URL
端点。这个博客帖子概述了几个方法,你可以用来在
ASP.NET
中干净地映射或重写
URL
,以及按照你自己的需求组织你的
URL
的结构。
为什么
URL
映射和重写很重要?
下面是开发人员想要对
URL
有更大的灵活性的最常见的场景:
1)
处理这样的情形:你要更改你的
web
应用中网页的结构,但你同时也要确保在你移动网页后,那些被人收藏的老
URL
不会成为死链接。重写
URL
允许你透明地将请求转交到新的网页地址而不出错。
2)
在象
Google
,
Yahoo
和
Live
这样的搜索引擎中提高你网站上网页的搜索相关性。具体地来说,
URL
重写经常能使你在你网站上网页的
URL
里更加容易地嵌入关键词,这么做往往会增加别人点击你的链接的机会。从使用查询字符串参数到使用完全限定
(fully qualified)
的
URL
也能在某些情形下提高你在搜索引擎结果中的优先顺序。使用强制
referring
链接使用同样的大小写
(same case)
和
URL
入口
(
譬如,使用
weblogs.asp.net/scottgu
而不是
weblogs.asp.net/scottgu/default.aspx)
的技术也能避免因跨越多个
URL
而造成的网页排名
(pagerank)
的降低
(avoid diluting your pagerank across multiple URLs)
,从而增加你的搜索结果。
在一个搜索引擎日渐驱动网站访问量的世界里,在你的网页排名上稍微得到一些提高就能给你的业务带来不错的投资回报
(ROI)
。逐渐地,这驱使开发人员使用
URL
重写以及其他
SEO(
搜索引擎优化
)
技术来优化网站
(
注,
SEO
是个步调很快的空间,增加你的搜索相关性的建议月月在演变
)
。想了解一些关于搜索引擎优化方面好的建议的话,我建议你阅读一下《
SSW Rules to Better Google Rankings (SSW的提高Google排名之要领)
》,以及
MarketPosition
关于《
how URLs can affect top search engine ranking (URL会如何影响顶级搜索引擎排名)
》的文章。
例程的
URL
重写场景
为这个博客贴子起见,我将假设我们将在一个应用里建造一套电子商务的产品目录网页,产品是按种类来组织的
(
譬如,图书,录像,
CD
,
DVD
等等
)
。
让我们假定一开始我们有个网页叫
Products.aspx
,通过查询字符串参数接受一个类别名称,相应地过滤显示的产品。与这个
Products.aspx
网页对应类别的
URL
看上去象这样:
http://www.store.com/products.aspx?category=books
http://www.store.com/products.aspx?category=DVDs
http://www.store.com/products.aspx?category=CDs
http://www.store.com/products.aspx?category=DVDs
http://www.store.com/products.aspx?category=CDs
但我们不想使用查询字符串来呈示每个类别,我们想修改应用,让每个产品类别对搜索引擎来说看上去象是一个独特的
URL
,并且在实际的
URL
中嵌入关键词
(
而不是通过查询字符串参数
)
。我们将在这个博客帖子剩下来的篇幅里,讨论一下达成这个目的我们可以采取的
4
种不同方法。
方法一:使用
Request.PathInfo
参数而不是查询字符串
我将示范的第一个方法根本不使用
URL
重写,而是使用
ASP.NET
中不太为人所知的一个特性,
Request
的
PathInfo
属性。为帮助解释这个属性的有用之处,考虑一下我们电子商店下面这些
URL
的情形:
http://www.store.com/products.aspx/Books
http://www.store.com/products.aspx/DVDs
http://www.store.com/products.aspx/CDs
http://www.store.com/products.aspx/DVDs
http://www.store.com/products.aspx/CDs
你会在上面这些
URL
中注意到的一个东西是,他们不再含有查询字符串值,取而代之的是,类别参数的值是附加到
URL
上的,是以
Products.aspx
网页处理器名称之后的
/
参数
值的方式出现的。然后,一个自动化的搜索引擎爬虫
(search engine crawler)
会把这些
URL
解释成三个不同的
URL
,而不是一个
URL
带有三个不同的输入值
(
搜索引擎是不理会文件扩展名的,只把它当作
URL
中的另一个字符而已
)
。
你也许很想知道怎么在
ASP.NET
中处理这个附加的参数的情形。好消息是,这非常简单。只要使用
Request
的
PathInfo
属性就可以了,该属性返回
URL
中紧随
products.aspx
后面的那部分内容。所以,对上面这些
URL
,
Request.PathInfo
会分别返回
“/Books”
,
“/DVDs”
,和
“/CDs”(
万一你想知道的话,
Request
的
Path
属性返回
“/products.aspx” )
。
然后,你可以轻易地编写一个函数来获取产品类别,象这样
(
下面这个函数去除前面的斜杠字符,只返回
“Books”
,
“DVDs”
,或
“CDs”)
:
Function GetCategory() As String
If (Request.PathInfo.Length = 0) Then
Return ""
Else
Return Request.PathInfo.Substring(1)
End If
End Function
If (Request.PathInfo.Length = 0) Then
Return ""
Else
Return Request.PathInfo.Substring(1)
End If
End Function
样例下载
:我建立的一个展示这个技术的样例应用可以
在这里下载
。这个样例和这个技术的很好的地方在于,为部署使用这个方法的
ASP.NET
应用,不需作任何服务器配置改动。在共享主机的环境里,这个技术也行之有效。
方法二:使用
HttpModule
实现
URL
重写
上述
Request.PathInfo
技术的替换方法是,利用
ASP.NET
提供的
HttpContext.RewritePath
方法。这个方法允许开发人员动态地重写收到的
URL
的处理路径,然后让
ASP.NET
使用刚重写过后的路径来继续执行请求。
譬如,我们可以选择向大众呈示下列
URL
:
http://www.store.com/products/Books.aspx
http://www.store.com/products/DVDs.aspx
http://www.store.com/products/CDs.aspx
http://www.store.com/products/DVDs.aspx
http://www.store.com/products/CDs.aspx
在外界看来,网站上有三个单独的网页
(
对搜索爬虫而言,这看上去很棒
)
。通过使用
HttpContext
的
RewritePath
方法,我们可以在这些请求刚进入服务器时,动态地把收到的
URL
重写成单个
Products.aspx
网页接受一个查询字符串的类别名称或者
PathInfo
参数。譬如,我们可以使用
Global.asax
中的
Application_BeginRequest
事件,来这么做:
void Application_BeginRequest(object sender, EventArgs e) {
string fullOrigionalpath = Request.Url.ToString();
if (fullOrigionalpath.Contains("/Products/Books.aspx")) {
Context.RewritePath("/Products.aspx?Category=Books");
}
else if (fullOrigionalpath.Contains("/Products/DVDs.aspx")) {
Context.RewritePath("/Products.aspx?Category=DVDs");
}
}
string fullOrigionalpath = Request.Url.ToString();
if (fullOrigionalpath.Contains("/Products/Books.aspx")) {
Context.RewritePath("/Products.aspx?Category=Books");
}
else if (fullOrigionalpath.Contains("/Products/DVDs.aspx")) {
Context.RewritePath("/Products.aspx?Category=DVDs");
}
}
手工编写象上面这样的编码的坏处是,很枯燥乏味,而且容易犯错。我建议你别自己写,而是使用网上现成的
HttpModule
来完成这项工作。这有几个你现在就可以下载和使用的免费的
HttpModule
:
这些模块允许你用声明的方式在你应用的
web.config
文件里表达匹配规则。譬如,在你应用的
web.config
文件里使用
UrlRewriter.Net模块
来把上面的那些
URL
映射到单个
Products.aspx
页上,我们只要把这个
web.config
文件添加到我们的应用里去就可以了
(
不用任何编码
)
:
<
?xml
version
="1.0"?>
< configuration >
< configSections >
< section name ="rewriter"
requirePermission ="false"
type ="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</ configSections >
< system.web >
< httpModules >
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>
</ httpModules >
</ system.web >
< rewriter >
< rewrite url ="~/products/books.aspx" to ="~/products.aspx?category=books" />
< rewrite url ="~/products/CDs.aspx" to ="~/products.aspx?category=CDs" />
< rewrite url ="~/products/DVDs.aspx" to ="~/products.aspx?category=DVDs" />
</ rewriter >
</ configuration >
< configuration >
< configSections >
< section name ="rewriter"
requirePermission ="false"
type ="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</ configSections >
< system.web >
< httpModules >
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"/>
</ httpModules >
</ system.web >
< rewriter >
< rewrite url ="~/products/books.aspx" to ="~/products.aspx?category=books" />
< rewrite url ="~/products/CDs.aspx" to ="~/products.aspx?category=CDs" />
< rewrite url ="~/products/DVDs.aspx" to ="~/products.aspx?category=DVDs" />
</ rewriter >
</ configuration >
上面的
HttpModule URL
重写模块还支持正则表达式和
URL
模式匹配
(
以避免你在
web.config
文件里硬写每个
URL)
。所以,不用写死类别名称,你可以象下面这样重写匹配规则,把类别名称动态地从任何
/products/[
类别
].aspx
组合的
URL
里取出来:
<rewriter>
<rewrite url="~/products/(.+).aspx" to="~/products.aspx?category=$1" />
</rewriter>
<rewrite url="~/products/(.+).aspx" to="~/products.aspx?category=$1" />
</rewriter>
这使得你的编码极其干净,并且扩展性非常之好。
这个样例和这个技术的很好的地方在于,为部署使用这个方法的
ASP.NET
应用,不需作任何服务器配置改动。在设置为中等信任安全等级
(medium trust)
的共享主机的环境里,这个技术也行之有效
(
只要把文件
FTP/XCOPY
到远程服务器就可以了,不需要安装
)
。
方法三:在
IIS7
中使用
HttpModule
实现无扩展名的
URL
重写
上述的
HttpModule
方法在你要重写的
URL
含有
.aspx
扩展名或者包含另一个被设置为
ASP.NET
处理的扩展名的情形下一切都工作。你这么做的话,不需要任何特定的服务器配置,你只要把你的应用拷贝到远程服务器,它会正常工作的。
但有的时候,你要重写的
URL
要么拥有一个不为
ASP.NET
处理的文件扩展名
(
譬如,
.jpg
,
.gif
,
或
.htm)
,要么根本没有扩展名。譬如,我们也许要把这些
URL
呈示成公开的产品目录网页
(
注意,它们没有
.aspx
扩展名
)
:
http://www.store.com/products/Books
http://www.store.com/products/DVDs
http://www.store.com/products/CDs
http://www.store.com/products/DVDs
http://www.store.com/products/CDs
在
IIS5
和
IIS6
中,使用
ASP.NET
处理上面这样的
URL
不是很容易。
IIS 5/6
使得在
ISAPI
扩展
(ASP.NET
就是这样一个扩展
)
里非常难以重写这些类型的
URLS
。你需要做的是使用
ISAPI
过滤器在
IIS
请求管道
(request pipeline)
的较早期实现重写。我将在下面的第四个方法里示范如何在
IIS5/6
实现这样的重写。
但好消息是,
IIS 7.0
使得处理这类情形容易之极。你现在可以在
IIS
请求管道的任何地方执行一个
HttpModule
,这意味着你可以使用上面的
URLRewriter 模块
来处理和重写无扩展名的
URL(
甚至是带有
.asp
,
.php
,或
.jsp
扩展名的
URL)
。下面示范了你在
IIS7
中该如何配置:
<
?xml
version
="1.0"
encoding
="UTF-8"?>
< configuration >
< configSections >
< section name ="rewriter"
requirePermission ="false"
type ="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</ configSections >
< system.web >
< httpModules >
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" />
</ httpModules >
</ system.web >
< system.webServer >
< modules runAllManagedModulesForAllRequests ="true">
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule" />
</ modules >
< validation validateIntegratedModeConfiguration ="false" />
</ system.webServer >
< rewriter >
< rewrite url ="~/products/(.+)" to ="~/products.aspx?category=$1" />
</ rewriter >
</ configuration >
< configuration >
< configSections >
< section name ="rewriter"
requirePermission ="false"
type ="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter" />
</ configSections >
< system.web >
< httpModules >
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" />
</ httpModules >
</ system.web >
< system.webServer >
< modules runAllManagedModulesForAllRequests ="true">
< add name ="UrlRewriter" type ="Intelligencia.UrlRewriter.RewriterHttpModule" />
</ modules >
< validation validateIntegratedModeConfiguration ="false" />
</ system.webServer >
< rewriter >
< rewrite url ="~/products/(.+)" to ="~/products.aspx?category=$1" />
</ rewriter >
</ configuration >
注意一下
<system.webServer>
内
<modules>
部分设置为
true
的
runAllManagedModulesForAllRequests
属性。这个属性确保来自
Intelligencia
的
UrlRewriter.Net
模块
(
是在
IIS7
正式发布前编写的
)
,会被调用,有机会重写到服务器的所有
URL
请求
(
包括文件夹
)
。上面的
web.config
文件非常酷之处在于:
1)
它在任何
IIS7
机器上都会工作,你不需要管理员在远程主机上启用任何东西,它也能在设置为中等信任安全等级
(medium trust)
的共享主机的环境场景下工作。
2)
因为我在
<httpModules>
和
IIS7
的
<modules>
部分同时配置了
UrlRewriter
,我既能在
VS
内置的
web
服务器
(
即
Cassini)
中,也能在
IIS7
下使用同样的
URL
重写规则。两者完全支持无扩展名的
URL
重写。这使得测试和开发非常容易。
IIS 7.0
将在今年的晚些时候作为
Windows Longhorn
服务器的一部分发布,将在几个星期内随
Beta3
版本的发布支持
go-live
许可。由于添加到
IIS7
中的所有的新宿主
(hosting)
特性,我们预期主机供应商将会非常快地开始积极提供
IIS7
账号,这意味着你应该很快就可以开始利用上述的无扩展名的
URL
重写支持。我们将在
IIS7 RTM
时段里发布一个为微软所支持的
URL
重写模块,该模板是免费的,你可以在
IIS7
上使用,并且这模块将对你
web
服务器上的所有内容的高级
URL
重写场景提供很好的支持。
方法四:在
IIS5
和
IIS6
中使用
ISAPIRewrite
来实现无扩展名的
URL
重写
如果你不想等到
IIS7
出来才利用无扩展名的
URL
重写,那么你最好的措施是使用
ISAPI
过滤器来重写
URL
。我知道有
2
个
ISAPI
过滤器方案,你也许要去看一下:
我没亲手用过上面的产品,虽然我听过到对这
2
个产品的好评。
Scott Hanselman
和
Jeff Atwood
最近都写了精彩的博客贴子讲述使用这些产品的体验,同时提供了一些如何在这些产品中配置匹配规则的例子。
Helicon Tech
的
ISAPI Rewrite
的规则使用跟
Apache
的
mod_rewrite
同样的句法,譬如
(取自Jeff的博客贴子)
:
[ISAPI_Rewrite]
# fix missing slash on folders
# note, this assumes we have no folders with periods!
RewriteCond Host: (.*)
RewriteRule ([^.?]+[^.?/]) http/://$1$2/ [RP]
# remove index pages from URLs
RewriteRule (.*)/default.htm$ $1/ [I,RP]
RewriteRule (.*)/default.aspx$ $1/ [I,RP]
RewriteRule (.*)/index.htm$ $1/ [I,RP]
RewriteRule (.*)/index.html$ $1/ [I,RP]
# force proper www. prefix on all requests
RewriteCond %HTTP_HOST ^test/.com [I]
RewriteRule ^/(.*) http://www.test.com/$1 [RP]
# only allow whitelisted referers to hotlink images
RewriteCond Referer: (?!http://(?:www/.good/.com|www/.better/.com)).+
RewriteRule .*/.(?:gif|jpg|jpeg|png) /images/block.jpg [I,O]
# fix missing slash on folders
# note, this assumes we have no folders with periods!
RewriteCond Host: (.*)
RewriteRule ([^.?]+[^.?/]) http/://$1$2/ [RP]
# remove index pages from URLs
RewriteRule (.*)/default.htm$ $1/ [I,RP]
RewriteRule (.*)/default.aspx$ $1/ [I,RP]
RewriteRule (.*)/index.htm$ $1/ [I,RP]
RewriteRule (.*)/index.html$ $1/ [I,RP]
# force proper www. prefix on all requests
RewriteCond %HTTP_HOST ^test/.com [I]
RewriteRule ^/(.*) http://www.test.com/$1 [RP]
# only allow whitelisted referers to hotlink images
RewriteCond Referer: (?!http://(?:www/.good/.com|www/.better/.com)).+
RewriteRule .*/.(?:gif|jpg|jpeg|png) /images/block.jpg [I,O]
注:使用
ISAPI
过滤器的一个坏处是,共享主机环境一般不允许你安装这样的组件,所以你要用它们的话,你要么需要一个专用的虚拟主机服务器,要么需要一个专用的主机服务器。但,如果你有一个主机计划允许你安装
ISAPI
的话,这会在
IIS5/6
下会提供最大的灵活性,让你过渡到
IIS7
推出为止。
在
URL
重写里处理
ASP.NET PostBack
大家在使用
ASP.NET
和重写
URL
时经常遇到的一个疑难杂症跟处理
postback
场景有关。具体地来说,当你在一个网页上放置一个
<form runat="server">
控件时,
ASP.NET
会自动地默认输出标识的
action
属性指向当前所在页面。当使用
URL
重写时,会出现这样的问题,
<form>
控件显示的
URL
不是原先请求的
URL(
譬如,
/products/books)
,而是重写过后的
URL(
譬如,
/products.aspx?category=books)
。这意味着,当你做一个
postback
到服务器时,
URL
不再是你原先干净利落的那个了。
在
ASP.NET 1.0
和
1.1
中,大家经常诉诸于继承
<form>
控件生成他们自己的控件,来正确地输出要使用的
action
属性。虽然这可以工作,但结果有点乱,因为这意味着你需要更新你所有的页面来使用这个另外的表单控件,而且有时在
Visual Studio
所见即所得设计器里也会遇上问题。
好消息是,在
ASP.NET 2.0
中,有个比较干净的诀窍你可以用来重写
<form>
控件的
action
属性。具体地来说,你可利用新的
ASP.NET 2.0控件适配器
扩展架构来定制控件的输出,用你提供的值来覆盖
action
属性的值。这不要求在你的
.aspx
页面里做任何编码改动
,而只要在你的
/app_browsers
文件夹里添加一个
.browser
文件,注册使用一个控件适配类即可输出新的
action
属性。
你可在这里查看一个我创建的样例实现,其展示了该
如何实现与URL重写协作的表单控件适配器(Form Control Adapter)
。它在我上面使用的第一个
(Request.PathInfo)
,第二个方法
(UrlRewriter.Net
模块
)
中都工作,它使用
Request
的
RawUrl
属性获取原先没改写过的
URL
来显示。而在第四个方法
(ISAPIRewrite
过滤器
)
中,你可以获取
ISAPI
过滤器保存在
Request.ServerVariables["HTTP_X_REWRITE_URL"]
中的原先的
URL
值。
我上面的
FormRewriter
类实现在标准的
ASP.NET
和
ASP.NET AJAX 1.0
网页上应该都工作
(
如果你遇上问题的话,告诉我一声
)
。
正确地处理
CSS
和图像引用
不少人在第一次使用
URL
重写时,有时会遇上一个疑难杂症,就是他们发现他们的图像和
CSS
样式表引用有时会停止工作。这是因为他们在
HTML
网页里有对这些文件的相对引用,当你开始在应用里重写
URL
时,你需要意识到浏览器经常会在不同的逻辑层次结构层上
(logical hierarchy levels)
请求文件,而不是实际存储在服务器上的东西。
譬如,如果我们上面的
/products.aspx
网页对
.aspx
网页里的
logo.jpg
有一个相对引用,但是通过
/products/books.aspx
这个
URL
来请求的,那么浏览器在显示网页时,将会发出一个对
/products/logo.jpg
的请求,而不是对
/logo.jpg
的请求。要正确地引用这个文件,确认你用根目录限定了
(root qualify)CSS
和图像引用
(“/style.css”
,而不是
“style.css”)
。对于
ASP.NET
控件,你也可以使用
“~”
句法从你应用的根目录来引用文件
(
譬如,
<asp:image imageurl="~/images/logo.jpg" runat="server"/>)
。
希望本文对你有所帮助