狂人日记·技术版

挖掘.NET的全部潜力

再谈使用UrlRewrite时修改form的action属性问题

虽然我一般属于只看不写的人,但距上一篇post竟然相隔一年多,不得不感慨时间真是快得恐怖啊……

最近创业,开展了一个Web 2.0项目,之前对Ajax、Url重写技术还有所谓的XHTML+CSS+DIV只停留在理论阶段,现在有机会付诸实践了,结果在玩UrlRewrite的时候就遇到了必然会遇到的ASP.NET的HttpForm自动将真实页面地址赋给action属性的问题。

网上Google了一下,解决办法就三种,添加客户端Java脚本,重载HttpForm或者Page类

老赵这篇帖子说明了添加客户端脚本以及重载HttpForm类的方法:http://www.cnblogs.com/JeffreyZhao/archive/2006/12/27/604373.html

而马哥(我恨用辈分称呼当网名的同志……下次我改名叫干爹,嘿嘿)这篇则说明了重载Page的方法:http://www.cnblogs.com/kaima/archive/2006/12/27/604758.html

UrlRewrite本身就有隐藏服务器处理细节的作用,这一部分要交给客户端来做感觉很别扭,所以用Java脚本的方案很快就被我否决了。

重载HttpForm类也被我否决了,通过Reflector查看代码,HttpForm的RenderAtttributes方法还包含处理客户端OnSubmit事件的代码,相当多的Web控件依赖这部分功能,去掉后就会破坏框架结构。MSDN竟然教大家用这种方法,果然MSDN还是一个需要去怀疑的东西。

马哥介绍的方法比较合理,但是我认为重载Page类也是在一般情况下应该避免的行为,一个是决定哪个页用新Page类哪页不用比较麻烦,如果为了省却麻烦,那么在web.config里设置pageBaseType属性也行,但是整个网站的页面都要过一下这个类也不太符合创业用网站的细节要求。

是不是有更好的办法呢?还真的有,是我今天在研究 ASP.NET CSS Friendly Adapters 的时候醒悟的。

这个解决方案基于上面马哥的方案修改,不过前提是必须有.NET 2.0的支持。

.NET 2.0框架给ASP.NET增加了几个特殊目录,其中有一个最容易被忽视的App_Browsers目录,这里是用来存放浏览器定义文件的,相关说明可以参考MSDN:http://msdn2.microsoft.com/zh-cn/library/ms228122(VS.80).aspx

在网站根目录创建App_Browsers目录,在里面建立一个新的文件起名 RewriteForm.browser ,其内容如下:

<browsers>
    
<browser refID="Default">
        
<controlAdapters>
            
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
                adapterType
="Kuang.HtmlFormAdapter" />
        
</controlAdapters>
    
</browser>
</browsers>

其中,browser节的 refID="Default" 属性是表示扩展系统原有的Default.browser文件(位于 %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers ),Default.browser 是全部浏览器定义的根,具体细节请参考MSDN说明。

Adapter的意思是适配器,在.NET领域表示在两个对象之间进行协调的对象,例如ADO.NET中众所周知的SqlDataAdapter类就是在SqlCommand和DataSet之间协调的Adapter。

ASP.NET 2.0带来了ControlAdapter的概念,意思是位于System.Web.UI.Control对象和ASP.NET之间的Adapter,同时也有PageAdapter,用于处理System.Web.UI.Page对象。

ControlAdapter并没有什么特殊的功能,只不过和重载Page类的方法相比较,前者提供了重载Web控件Render方法的能力而又不需要继承该控件,并且可以只针对特定的控件例如这里的HtmlForm类。而在马哥的方法中,如果有别的控件也用了action属性,就会被错误的改写。在
RewriteForm.browser 文件中,通过 <adapter controlType="System.Web.UI.HtmlControls.HtmlForm" adapterType="Kuang.HtmlFormAdapter" /> 这行,我指定了要重载HtmlForm类,并且提供了我自定义的ControlAdapter类的类型 Kuang.HtmlFormAdapter。

下面这个是自定义的ControlAdapter类的代码:

using System;
using System.Web.UI.Adapters;

namespace Kuang {
    
public class HtmlFormAdapter : ControlAdapter {
        
protected override void Render(System.Web.UI.HtmlTextWriter writer) {
            
base.Render(new FormRewriteTextWriter(writer));
        }
    }
}

和重载Page类的手段一样,这段代码也引用了一个自定义的 HtmlTextWriter 类,以下是该类的实现代码,我自己做了一定的修改:

using System;
using System.IO;
using System.Web;
using System.Web.UI;

namespace Kuang {
    
public class FormRewriteTextWriter : HtmlTextWriter {
        
public FormRewriteTextWriter(TextWriter writer) : base(writer) {
            
if(writer is HtmlTextWriter)
                
this.InnerWriter = (writer as HtmlTextWriter).InnerWriter;
            
else
                
this.InnerWriter = writer;
        }

        
public override void WriteAttribute(string name, string value, bool fEncode) {
            HttpContext context 
= HttpContext.Current;
            
object rewroteAlready = context.Items["FormActionRewroteAlready"];
            
if(name == "action" && rewroteAlready == null) {
                value 
= context.Request.RawUrl;
                context.Items[
"FormActionRewroteAlready"= new object();
            }
            
base.WriteAttribute(name, value, fEncode);
        }
    }
}

把以上两段代码放入到网站的App_Code目录下,就大功告成了,这个方法一个特别的优点是,不需要改动原来网站的任何代码,连 web.config 都不用改。

大家可以尝试一下,欢迎交流心得。


BTW:老赵提到的UpdatePanel和Form并存是不能提交第二次的问题,我没有遇到过,难道是因为我的UpdatePanel是嵌在Form里面的而不是Form嵌在UpdatePanel里面的缘故?

posted on 2007-04-23 00:27 狂人 阅读(5144) 评论(23) 编辑 收藏

评论

#1楼 2007-04-23 03:08 Jeffrey Zhao      

UpdatePanel必须嵌套在Form里,估计是因为您在Rewrite后URL路径的级别没有改变。  回复 引用 查看   

#2楼 2007-04-23 03:08 Jeffrey Zhao      

现在用Adaptor的方法已经比较常见了,呵呵。  回复 引用 查看   

#3楼[楼主] 2007-04-23 03:16 狂人      

路径级别是变的,可能我测试的不够仔细,等有机会我再尝试一下

博客园貌似没人提到Adapter这种方法,害我花费一整个通宵去研究,刚才Google了一下,发现国外很多站点都有提到,我就当给博客园一个补充,也算不白费自己研究的工夫
 回复 引用 查看   

#4楼 2007-04-23 09:36 king[未注册用户]

好文章。
但我还是不知道怎么调用这两个类!
笨啊。
 回复 引用   

#5楼[楼主] 2007-04-23 09:39 狂人      

@king
把RewriteForm.browser放到App_Browsers目录,两个类文件放到App_Code目录,然后就不用管了,剩下的都是系统自己的活儿了
 回复 引用 查看   

#6楼 2007-04-23 09:41 king[未注册用户]

噢,原来就这么简单!
强!
 回复 引用   

#7楼 2007-04-23 09:42 Ariel Y.      

貌似不能应用在Web Project Application中?  回复 引用 查看   

#8楼[楼主] 2007-04-23 09:48 狂人      

@Ariel Y.
可以的
但是有一点,Web Project Application不能有App_Code目录,这样会导致代码被编译两遍,一次是在发布的时候,一次是网站被访问的时候,会导致一些诡异的问题
所以如果是Web Project Application,把App_Code目录随便改成别的非特殊名字就可以了,例如就叫Code,或者把两个类文件跟别的源代码放在一起
 回复 引用 查看   

#9楼 2007-04-23 09:55 ken[未注册用户]

怎么我用了,那个路径还是一样啊!
浏览器的URL没变过!
 回复 引用   

#10楼[楼主] 2007-04-23 10:00 狂人      

@ken
这不是关于Url Rewrite实现的帖子,而是关于实现了Url Rewrite后所带来的问题的帖子
而你要的关于Url Rewrite的实现,请参考:http://www.google.com/search?q=ASP.NET+Url+Rewrite
 回复 引用 查看   

#11楼 2007-04-23 14:07 Jeffrey Zhao      

@狂人
其实我那贴的回复中就有提到阿。:)
 回复 引用 查看   

#12楼 2007-04-23 19:35 玉开      

不错  回复 引用 查看   

#13楼 2007-04-23 23:19 studydaybyday[未注册用户]

请教一下,怎么用VB来写这一段。  回复 引用   

#14楼[楼主] 2007-04-24 00:46 狂人      

@studydaybyday
翻译成VB很容易,但是原谅我无法帮您做到
建议您先用C#编译成类库后,用Reflector来完成这件事儿
Reflector下载:http://www.aisto.com/roeder/dotnet/Download.aspx?File=Reflector
 回复 引用 查看   

#15楼 2007-04-24 18:27 阿当

谢谢,很有用,刚好用上:)  回复 引用   

#16楼 2007-06-05 17:18 oyo[未注册用户]

我的提示:无法创建类型“Kuang.HtmlFormAdapter”。
这是怎么回事?
 回复 引用   

#17楼 2007-08-14 16:01 Rex[未注册用户]

编译通过,但是有个问题,我本机怎么都没事,不用这些修正方法也都可以,就是放到服务器就不行,请问这是为啥? 我本机做服务,同网的其他机器访问也都可以,奇怪了?????rex_fa@hotmail.com  回复 引用   

#18楼[楼主] 2007-08-14 16:28 狂人      

@Rex
服务器是.NET 2.0么?
 回复 引用 查看   

#19楼 2007-12-21 13:26 江大鱼      

http://www.cnblogs.com/jzywh/archive/2007/12/20/urlrewriteaction.html  回复 引用 查看   

#20楼 2007-12-28 17:27 windwind[未注册用户]

为什么用了adapter后 ascx的Page_Load会执行两遍???  回复 引用   

#21楼 2008-03-23 15:44 lv_1999[未注册用户]

action是改了.. 不过页面的服务器控件的事件代码不会被执行? 能解决吗??  回复 引用   

#22楼 2009-11-29 23:36 你好楼主      

翻到了您这篇旧闻,但是对我帮助很大。URL重写以后ACTION 更改的很好。

现在有个困扰我的问题是,我原来在URL里面加入了中文,网页格式为UTF-8,以前的传递参数 可以传递中文。但是现在用了这个以后,中文传过去以后,ACTION就是乱码了,我弄了两天也没解决。

希望能够赐教。谢谢了!!!
 回复 引用 查看   

#23楼 2011-01-24 17:27 zhiatyun      

使用你的代码后(即HtmlFormAdapter相关) 每次都会执行default.aspx的代码 很奇怪 比如order.aspx会莫名的跳到去执行default.aspx的后台代码 请告知原因 谢了  回复 引用 查看