本文将通过实例比较ASP.NET下的三种典型URL重写方案——ISAPI重写(使用开源组件IIRF),ASP.NET2.0内置的urlMappings和基于自定义HTTPModule的URL重写(使用NBear.Web中的UrlRewriteModule实现),并探讨URL重写中可能遇到的陷阱及处理办法。下载示例程序源码需要手动为UrlRewriteSample目录添加一个到http://localhost/UrlRewriteSample的同名虚拟目录,允许匿名访问,并设置目录默认页为default.aspx。另外,为了启用IIRF的URL重写支持,需要将UrlRewriteSample/bin目录下的IsapiRewrite4.dll添加为IIS默认网站的ISAPI过滤器。设置重写规则注意,我们的演示程序中将混合使用三种方式的URL重写,因此,需要为三种实现分别设置一些URL重写规则:1、IIRF,对于IIRF,对应于IsapiRewrite4.dll,在相同的目录会有一个IsapiRewrite4.ini文件,除了默认的一些设置,我们在文件末尾添加了几条自定义规则如下:# Custom RewriteRulesRewriteRule ^/UrlRewriteSample/test(.*).aspx /UrlRewriteSample/Default.aspx?page=$1RewriteRule ^/UrlRewriteSample/folder/(.*).aspx /UrlRewriteSample/Default.aspx?folder=$1RewriteRule ^/UrlRewriteSample/folder/? /UrlRewriteSample/Default.aspx?folder=default熟悉正则表达式的朋友应该很容易理解上面这三条规则。规则一将形如testXXX.aspx这样的页面访问,重写为Default.aspx?page=XXX这样的页面;规则二将形如folder/XXX.aspx的路径,重写为Default.aspx?folder=XXX这样的页面;规则三将不带任何文件的folder目录的访问,重写为Default.aspx?folder=default这样的页面。2、urlMappings是ASP.NET2.0内置支持的URL重写配置块,它应该包含在web.config的<system.web>配置块中。但是,这个内置的URL重写支持不支持正则表达式,因而只能用来实现一对一的路径和页面的重写。urlMappings的配置内容包含在下面的Web.config文件中。3、NBear.Web.Modules.UrlRewriteModule则是NBear中实现的一个基于HTTPModule的URL重写实现,它允许使用正则表达式来描述重写规则。
注意,代码中包含了urlMappings配置和用于NBear.Web.Modules.UrlRewriteModule重写规则。为了比较着几种重写方案,正则表达式基本上是和前面的IIRF定义中的规则类似的。页面测试定义完这些重写规则,我们就可以试着在页面中使用它们了。例如,如果我们写一个测试页面如下:Default.aspx
注意,Default.aspx页面会输出当前呈现的实际页面及其QueryString参数。运行该页面,分别点击页面中的链接,我们会看到,貌似所有的URL重写一切正常。但是,当试着点击页面中的按钮,我们马上会发现,页面postback后,浏览器地址栏中的链接变成了那个被重写后的地址,而不是,原来显示于地址栏的虚拟地址了。这是一个严重的不一致,没道理我点击页面的按钮,在没有跳转到其他页面的情况下,地址栏显示另一个页面地址,不是吗?要解决这个问题,我们只需要为form添加一个onsubmit事件处理如下:<form id="form1" runat="server" onsubmit="this.action=document.location.href">添加该事件处理,就能在页面postback提交之前,重置页面的地址。为前面的页面添加onsubmit之后,我们发现,postback不再会改变地址栏地址显示了。太棒了~~。。。先别沾沾自喜太多,你确认试过点击最后一组链接中的buildin default page和section default page了吗?你会发现,这两个链接根本不能显示。为什么呢?为什么类似的folder default page可以正常显示,而另两个不能显示呢?回到前面的规则定义部分,我们就能发现,folder default page使用的是由IIRF这个ISAPI定义的规则,而另两个则使用的是内置于ASP.NET2.0的HTTPModule的重写规则(本质上,urlMappings也是使用HTTPModule来实现重写的,所以,除了不支持正则表达式之外,它也包含自定义HTTPModule方式实现的所有缺点)。在IIS的ISAPI层面,是可以截获所有的页面请求的,哪怕指定的页面、目录根本不存在。但是,ASP.NET解析器则只有在对页面的请求被IIS转发过来时,才能处理。我们知道,IIS可以忽略对链接的虚拟目录是否存在的检测,但是,却无法检测非ASP.NET支持的文件扩展名的链接(我们固然可以在IIS中将所有类型的扩展名都映射到ASP.NET解析器,但是,如果我们有设置IIS的权限,为什么还要用性能更低,限制更多的ASP.NET方式的URL重写,而不使用基于ISAPI方式的重写呢?)。所以,为了让这两个不能显示的页面能正常显示,一方面,我们要在IIS中设置默认页,如default.aspx,另一方面,需要让IIS对某个不带aspx扩展名的链接,如这里只包含某个目录的名称的链接转发到默认页。要做到这一点,我们需要在我们的应用程序中,为buildin和section分别将两个对应的目录,并且,在目录中创建两个空的default.aspx页面。尽管这样的default.aspx页面实际上永远不会被真正执行,但是有他们的帮助,就能让IIS顺利地将页面请求转发至ASP.NET解析器,从而,使得基于HTTPModule的URL重写规则,被执行。好了,创建这两个目录及default.aspx文件,我们就能修复该问题了。。。。别急着庆祝,还是多做点测试为好。:)我们来对页面上的链接反复点击点击,folder page -> section page -> folder page -> section page...等等,打住,看到浏览器地址栏发生了什么吗?这不是恐怖活动,但是。。。也差不多了。我们看到我们可爱的地址,变成了...folder/section/folder/section...aspx。想想是为什么呢?看看我们的aspx文件。。。我想你一定想到了。对了,都是相对路径惹的祸!我们可爱的的相对路径一顶是同学们最常使用的,但是,浏览器在处理相对路径时,是以浏览器上接受的url地址为基础进行计算的,也就是说,如果当前的地址为folder/1.aspx,那么,很显然,./section/2.aspx这个页面,对应的自然是folder/section/2.aspx了,问题就出在这儿了!没有URL重写时,不会有这样的情况出现。但是URL重写,并且,将一个带假目录的虚拟地址重写到一个不带假目录的页面时,由于浏览器客户端和服务端此时的当前页面计算方法是不同的,就会发生相对路径的匹配错误问题!真实很严重的问题啊!解决的办法,只有使用绝对路径!但是,我们当然不会傻到对每个链接直接使用绝对路径的,呵呵:)将Default.aspx中的所有相对路径都使用Page.ResolveUrl进行包装如下,在输出页面时就将地址转换为绝对路径,就能解决这个恐怖的相对路径陷阱了。当然,也别忘了加上onsubmit事件处理代码:Default.aspx
好了,再试一试点击页面上的链接和按钮,多点几次,再点,再点。。。。。。呼~终于搞定了吧!^-^
posted on 2006-09-11 13:31 Teddy's Knowledge Base 阅读(11426) 评论(29) 编辑 收藏 网摘 所属分类: Web Dev.NBear
使用IIRF的性能似乎是最好的,但是它的缺点也很明显,与asp.net没有实际的关系,自定义能力可能没那么强。 asp.net 2.0内置的UrlMapping并不适用于多种场合下,在这种情况使用第三方HttpModule组件似乎是一种最好的选择,如果需要有更强的自定义能力,仍然可以自已实现,利用(HttpContext.RewritePath方法和正则表达式),具体可以参考CommunityServer的实现。 另外,不知道Teddy有没有注意到在asp.net 2.0中,使用RewritePath有一个BUG,见http://www.cnblogs.com/hjf1223/archive/2006/08/21/482356.html ,虽然标题是《由"~/"引发的搜索问题》,但后来发现,实际上是Asp.net 2.0进行Url重写时的一个BUG。 回复 引用 查看
@阿不 ~实际上只是在服务端被转换成了相对路径,注意还是相对路径! 所以,本质上使用它就和直接使用相对路径一样,避免不了我文中提到的相对路径陷阱问题。 回复 引用 查看
还要支持正则表达式就完美了,HttpModule 回复 引用
我不知道这个BUG是不是asp.net 2.0专有的,但是确实是我解决的方式是通过调用ResolveUrl方法来把"~/"解析为绝对路径就可以了,但是这样确实是有点太麻烦了。所以大家都认为它是个BUG。 回复 引用 查看
我觉得用正则来写,自定义能力已经足够强了吧,毕竟Apache也就是这个水平了。 将Default.aspx中的所有相对路径都使用Page.ResolveUrl进行包装 //没有更好的办法了吗? 回复 引用 查看
@阿不 我不认为这是一个bug,就如我文中所说的,计算向对路径的过程是在客户端浏览器,由浏览器根据重写前的URL计算的,而重写前的URL我们无法预知其格式,因此,如果你的重写前的地址包含多个你所谓的级,那么,计算得来的如http://localhost/section1/section2/a.aspx页面中的一个到~/b.aspx页面的相对路径肯定就是../../b.aspx了的!! 回复 引用 查看
@Teddy's Knowledge Base 或许我们可以不认为它是个BUG,但是确实很奇怪的是,这个问题在普通浏览器中是不会出现的,而只有在 Search Enginer搜索站点的时候才会出现这个问题的。 回复 引用 查看
@阿不 如果真像你说的“只有在 Search Enginer搜索站点的时候才会出现这个问题”的话,那也该是Search Enginer工具的bug,怎么能说是ASP.NET的bug呢?:) 回复 引用 查看
@Teddy's Knowledge Base :( ,这样说吧。 同样Url Rewrite实现,同样的Search Enginer,在asp.net 1.1下不会有问题,但在asp.net 2.0会出错?那你能说是Search Enginer的bug? 我描述的不是很清楚,你可以看: http://weblogs.asp.net/dfindley/archive/2006/09/05/Problems-with-RewritePath-and-Search-Engines_2E00_.aspx http://todotnet.com/archive/2006/07/01/7472.aspx (我还没有完全看懂呢。) 回复 引用 查看
@阿不 看了一下你给的这个文章,他讲的问题的关键和URLRewrite没有本质关系。他说的是cookiless forms authentication的问题。ASP.NET2.0支持一种新的不依赖于cookie的session支持。默认情况下,ASP.NET自动判断客户端浏览器是否支持cookie,如果支持,则和asp.net1.1没什么区别。但是,如果浏览器不支持cookie,或者禁用cookie时,用户访问一个使用session的网站时,会有一个自动生成的sessionid被自动嵌到原来的url中,这就很可能会导致不支持cookie的Search Enginer得到错误的链接地址了。 但是就像原文所说的,如果显式的在web配置文件中指定网站必须使用cookie(默认设置是自动检测是否支持cookie),就能避免内迁进url的sessionid,就不会有问题了。 回复 引用 查看
@Teddy's Knowledge Base 谢谢你详细的解释,我也理解到与cookieless有关了,但是没有真正理解它的内在原因。但是我想这仍然可能会有大量的开发者没有注意到这个问题,所以在使用asp.net 2.0的Url Rewrite时,要特别注明。 回复 引用 查看
字体这么小,怎么看! 回复 引用 查看
@发哥 字体不小的,大家都看得挺好。估计是你的浏览器的问题,你可以试试在页面上按住Ctrl键,同时滚动鼠标的滚轮,这样可以改变页面字体默认大小,很可能你以前不小心这样把浏览器默认字体改小了,你可以用该方法改回来。 回复 引用 查看
使用开源组件IIRF 可以从哪里下源代码? 回复 引用
@小鬼 IIRF的官方站点: http://cheeso.members.winisp.net/IIRF.aspx 回复 引用 查看
学习 回复 引用 查看
<Rule key="^/UrlRewriteSample/sample(.*).aspx" value="/UrlRewriteSample/Default.aspx?page=$1" /> Teddy 的URL规则是通过正则表达式的分组然后再$1代替组一的吧?那不知是否支持多个分组,然后有组二($2)、组三($3)的形式呢?不过这种情况好像不怎么见到吧 另外有个问题:如果像写了几十个甚至上百个<Rule...>标签,而又要通过正则匹配的方式判断并映射,会不会对整个网站的效率影响很大? 回复 引用
@czyl 当然支持$2,$3。。。的,这里就是采用的标准的.Net类库的RegEx.Replace功能。 如果规则过多,肯定会影响性能的。所以使用url重写还是要时刻注意性能问题。 回复 引用 查看
这个能不能实现二级域名啊? 本人还在入门中呵。http://dotnet.blyct.com/QuickStartv20/default.aspx 回复 引用
MS的文档中对form的action处理是用另一种方法,代码如下: public class ActionlessForm : System.Web.UI.HtmlControls.HtmlForm { /// <summary> /// The RenderAttributes method adds the attributes to the rendered <form> tag. /// We override this method so that the action attribute is not emitted. /// </summary> protected override void RenderAttributes(HtmlTextWriter writer) { // write the form's name writer.WriteAttribute("name", this.Name); base.Attributes.Remove("name"); // write the form's method writer.WriteAttribute("method", this.Method); base.Attributes.Remove("method"); // remove the action attribute base.Attributes.Remove("action"); // finally write all other attributes this.Attributes.Render(writer); if (base.ID != null) writer.WriteAttribute("id", base.ClientID); } } 它的原理很简单,就是重写基类的RenderAttributes(),清空form的action属性。 回复 引用
这是作者Sample的原例: 在 ASP.NET 中执行 URL 重写 http://www.microsoft.com/china/msdn/library/webservices/asp.net/URLRewriting.mspx 回复 引用 查看
使用相对应用程序根目录就行了。而不是相对路径,绝对路径,在重写前都要转换成相对应用程序根目录来进行的。 回复 引用
Teddy同学的这个方案有两小点我觉得不妥:为了让UrlRewriting的Mapping方案具有可读性,应该适当在表示方式上尽量兼容大家都习惯的或是标准的名称,如: <UrlMappings> <add url="community/(\d+).html" mappedUrl="~/template.aspx?action=community.default&cid=$1"/> <add url="group/(\d+)/create.html" mappedUrl="~/template.aspx?action=group.create&cid=$1"/> </UrlMappings> 当然,这只是建议。 Form 的Action属性可以通过服务器端继承一个HtmlTextWriter来写。 这样不必在客户端写onsubmit来实现,因为在一定程度上已经违反了URL重写的我认为是原则的原则:不能暴露真实URL,显然这是违反了的。 HtmlTextWriter的用法我是参考Cuyahoga的原理来完成的,这里需要两个步骤: 继承一个HtmlTextWriter对象,设计一个FormFixer对象来完成对URL重写后Form的Action属性的纠正。这个FormFixer对象是给BasePage专用的,所以可以声明为内部类; 继承一个Page对象,设计自己的BasePage,然后该项目所有继承自Page的类或aspx文件都直接或间接继承自这个BasePage; 为了方便而把这两个类写在一起了,其中FormFixer对象直接作为BasePage的内部类,关键的方法和代码部分我用粗体表示: using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Babatang.Web { public class BasePage : Page { protected override void Render(HtmlTextWriter writer) { writer = new FormFixer(writer); base.Render(writer); } } internal class FormFixer : HtmlTextWriter { private bool _isInForm = false; public FormFixer(System.IO.TextWriter writer) : base(writer) { } public FormFixer(System.IO.TextWriter writer, string tagString) : base(writer, tagString) { } public override void WriteBeginTag(string tagName) { if (string.Compare(tagName, "form") != 0) this._isInForm = true; base.WriteBeginTag(tagName); } public override void WriteAttribute(string name, string value, bool fEncode) { if (this._isInForm && name.Equals("action")) value = HttpContext.Current.Request.RawUrl; base.WriteAttribute(name, value, fEncode); } } } 另外贴个小技巧: 如果是在VS2003 .NET 1.1 下我暂时没发现什么,查找替换的方法足矣; 如果是在ASP.NET 2.0 下并且没有使用后台文件的话可以在web.config文件中加上一段配置就可以实现,其中关键部分用粗体表示: <pages enableViewState="false" validateRequest="false" maintainScrollPositionOnPostBack="true" pageBaseType="Babatang.Web.BasePage,Babatang.Web" userControlBaseType="Babatang.Web.BaseControl,Babatang.Web" theme="babatang"> 该站点下所有单页文件都默认继承这个BasePage了。当然,如上面所写的userControlBaseType属性一样,UserControl也可以象Page这么干。 如果是 .NET 2.0 下并且使用了后台文件的话我还不知道有什么好办法,因为即使如第二条那样在web.config中声明了页面基类的话在aspx.cs文件中仍然是继承那个默认的Sytem.Web.UI.Page这个对象了,仍然只有查找替换的办法。 这样,我的一个虚拟URL:http://www.babatang.com/community/mjxc/default.aspx 实际上就是几乎真实的URL了。即使看源代码也没有其真实URL的影子,对于访客来说这就是URL。 这是俺在03-04年翻译的一篇文章,http://www.cnblogs.com/xspin/articles/30788.aspx,比MSDN翻译的早了许多,没事儿看看吧,Scott的这篇原文让我学了很多东西,推荐看看。 Blog一直都不习惯,就当是论坛回帖吧,有问题也请继续回帖。可惜这里不能顶 回复 引用 查看
另外URL Rewriting前后的参数保留问题有时候也要考虑,比如在页面上做一个分页,又只想把页码做为参数而不参与URL重写的话(http://www.babatang.com/community/mjxc/default.aspx?offset=2),同时如果正好要重写出一个参数和虚拟URL上的参数重合的话还要考虑是否覆盖的问题。 回复 引用 查看
在Web.Config文件中 <UrlRewriteRules> <Rule key="^/UrlRewriteSample/sample(.*).aspx" value="/UrlRewriteSample/Default.aspx?page=$1&id=$2" /> </UrlRewriteRules> 我用 Request.QueryString["ID"].ToString() 取不到id的值。 在页面中怎么得到'id'的值? 回复 引用
etoo games 回复 引用
很好用啊。关于路径,我觉得并非一定要用绝对的,比如<Rule key="/show/([a-z0-9]*).aspx" value="/A.aspx?cid=$1" />// 若页面中链接为相对的alter/(*).aspx,那么再加一个处理它的:<Rule key="/show/alter/([a-z0-9]*).aspx" value="/B.aspx?cid=$1" />也就是说,将错就错,URL往长里弄 回复 引用
谢谢各位大大,学习中 回复 引用 查看
昵称: [登录] [注册]
主页:
邮箱:(仅博主可见)
验证码: 看不清,换一个
评论内容:
登录 注册
[使用Ctrl+Enter键快速提交评论]
Powered by: 博客园 Copyright © Teddy's Knowledge Base