Hello World
Spiga

UpdatePanel与UrlRewrite

2006-12-27 00:42 by 老赵, 8224 visits

今天收到邮件,被问及为什么UpdatePanel如果结合了UrlRewrite就会出现问题。一开始我不以为然,由于我也曾经在UrlRewrite时使用过UpdatePanel,没有出现过问题。但是收到对方打包的代码后,发现这个问题的确重现了,如果直接访问目标页面就不会有任何问题。因为当时在公司,没有仔细地去研出错的原因。在回家的路上,脑子里一遍一遍地模拟着UpdatePanel的实现过程,却没有察觉到有任何不妥。最后还是忍不住,坐在公交车上的时候就打开笔记本,仔细的寻找问题所在。公交车实在晃得厉害,还好最终在我呕吐之前找到了问题,刚才的思考还是棋差一着。

 

重现问题:

现在我将重现那个问题。在原来的代码中使用了NBear的UrlRewriteModule,为了简单起见,我使用了最普通的UrlRewrite的做法来得到相同的效果,尽量避免有些朋友(包括我)因为不熟悉NBear而妨碍文章内容的理解。

首先,新建一个ASP.NET AJAX Enabled Web Site。创建一个文件~/SubFolder/Target.aspx,内容如下:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Target Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <%= DateTime.Now.ToString() %>
                <asp:Button ID="Button1" runat="server" Text="Refresh" />
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>
</body>
</html>

 

然后再创建一个Global.asax,提供Application_BeginRequest方法,在其中实现Url Rewrite,如下:

void Application_BeginRequest(object sender, EventArgs e)
{
    HttpContext context = (sender as HttpApplication).Context;
    if (context.Request.Path.Contains("Source.aspx"))
    {
        context.RewritePath("SubFolder/Target.aspx", false);
    }
}

 

这样,当我们访问~/Source.aspx文件时,则会被Rewrite至~/SubFolder/Target.aspx,打开页面,一切正常:

点击Refresh按钮之后,时间被更新了。然后当我们再点击按钮之后,错误发生了:“Sys.WebForms.PageRequestManagerServerErrorException: An unknown eror occurred while processing the request on the server. The status code returned from the server was: 12031”。

 

分析问题:

发生这个问题的原因是因为Url Rewrite更新了Form提交的地址,而UpdatePanel又将这地址的改变反映到了页面上。

在第一次打开页面时,我们可以看到页面的源文件中<form />元素的action已经不是我们访问的Source.aspx,而是Url Rewrite后的目标文件:

...
<form name="form1" method="post" action="SubFolder/Target.aspx" id="form1">
...
</form>
...

 

还好我们使用了Partial Rendering,只要“目标”是正确的,UpdatePanel依旧能够正确地发送和获取数据,然后更新页面。因此,在点击Refresh按钮之后,页面被正确更新了。可是,我们form元素的action也变了,使用Web Development Helper和IE Dev Toolbar便一目了然:

由于我们在进行异步PostBack时,直接访问了~/SubFolder/Target.aspx,因此在生成的Form对象其action值为Target.aspx。于是乎,UpdatePanel兢兢业业地将客户端form元素的action也进行了修改。这样就让我们再次提交时访问了一个不存在的页面,错误就再所难免了。

 

解决问题:

既然发现了问题所在,那么解决起来自然也会得心应手。我们只要在响应Sys.Application的load事件即可,它会在页面第一次加载时,以及每次Partial Rendering之后被触发,我们在这时候修改页面中form元素的action属性即可,如下:

Sys.Application.add_load(function()
{
    var form = Sys.WebForms.PageRequestManager.getInstance()._form;
    form._initialAction = form.action = window.location.href;
});

至于为什么应该这样获得页面中的form元素,_initialAction又是什么,以及为什么要设置它,就要牵涉到UpdatePanel的实现方式,在这里就不多作解释了。只要页面中放置了这么一小段代码,这个问题就被解决了。

 

深入问题:

造成这个问题的原因,其实就是因为在Url Rewrite之后,form元素的action并非客户端请求的地址,而是Url Rewrite的目标地址。如果我们没有使用Partial Rendering,而是使用了最传统的PostBack,虽然不会造成页面功能的破坏,但是在PostBack之后,用户就会发现地址栏的内容变了,直接变成了目标地址。这可不是我们希望看到的结果,既然Rewrite了,就把它Rewrite到底。当然,我们依然可以使用上面提到的办法,使用JavaScript来修改form元素的action,但是这个做法实在不够“美观大方”,而且用户从HTML源文件中也可以看到我们Url Rewrite的目标地址,不是吗?

如果我们能够在服务器端设置Form的action就好了,可惜System.Web.UI.HtmlControls.HtmlForm类不允许我们这么做。不过还好,我们用的是ASP.NET,我们用的是面向对象的编程模型。于是我们“继承”System.Web.UI.HtmlControls.HtmlForm,实现一个自己的Form控件:

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);
        }
    }
}

 

然后我们就可以在页面中使用它了。当然,在这之前,我们需要在页面(或Web.config)里注册它:

<%@ Register TagPrefix="skm" Namespace="ActionlessForm" 
    Assembly="ActionlessForm" %>
...
<skm:Form id="Form1" method="post" runat="server">
...
</skm:Form>
...

 

至此,我们已经不需要在页面里编写一段“巧妙”的JavaScript了,Url Rewrite之后form元素的action问题被解决了。

(“深入问题”参考了MSDN上一篇文章的部分内容:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/urlrewriting.asp

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

44 条回复

  1. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-27 01:12:00

    在MSDN的文章上,它就提供了一个Actionless的HtmlForm,强行把action属性删掉,action就相当于当前页面路径了。

  2. 老赵
    admin
    链接

    老赵 2006-12-27 01:21:00

    @Cat Chen
    是啊,我最后举的例子就是这个。
    // 格式全乱了……算了一觉醒来再说……

  3. 老赵
    admin
    链接

    老赵 2006-12-27 01:42:00

    @Cat Chen
    还是现在改好算了,呵呵。

  4. 虫子[匿名][未注册用户]
    *.*.*.*
    链接

    虫子[匿名][未注册用户] 2006-12-27 08:54:00

    楼主学学aspnetforums里面关于URLRewrite的设置,
    这样保证不会出问题。

  5. 迷途小猪儿
    *.*.*.*
    链接

    迷途小猪儿 2006-12-27 10:07:00

    试试这个:www.urlrewriting.net

  6. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-27 10:11:00

    @虫子[匿名]
    那东西现在已经是Community Server的一部分了,它广泛应用了UrlRewrite技术。

  7. anikin
    *.*.*.*
    链接

    anikin 2006-12-27 10:47:00

    奇怪了,我试了一下,怎么没有发生这样的问题?

  8. Kai.Ma
    *.*.*.*
    链接

    Kai.Ma 2006-12-27 11:04:00

    不错,我觉得写一个class URLRewritePage : Page 这样更方便喔。

  9. 阿不
    *.*.*.*
    链接

    阿不 2006-12-27 11:14:00

    似乎去掉Action属性连首次postback也不行,我的做法是将action的值改为:
    Page.Request.RawUrl或Page.Request.Url.PathAndQuery
    应该就没问题了。

  10. Kai.Ma
    *.*.*.*
    链接

    Kai.Ma 2006-12-27 12:18:00

  11. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-27 12:24:00

    对了,其实用ControlAdapter去删除action属性的,那就不用改动HtmlForm了。

  12. 老赵
    admin
    链接

    老赵 2006-12-27 20:25:00

    @虫子[匿名]
    能否简单介绍一下呢?:)

  13. 老赵
    admin
    链接

    老赵 2006-12-27 20:26:00

    @迷途小猪儿
    谢谢

  14. 老赵
    admin
    链接

    老赵 2006-12-27 20:27:00

    @Cat Chen
    Url Rewrite……它的兴起还是因为SEO啊……

  15. 老赵
    admin
    链接

    老赵 2006-12-27 20:27:00

    @anikin
    您是完全按照我文章里的重现方式吗?

  16. 老赵
    admin
    链接

    老赵 2006-12-27 20:28:00

    @迷途小猪儿
    哎,现在上不了国外的……:(

  17. 老赵
    admin
    链接

    老赵 2006-12-27 20:29:00

    @Kai.Ma
    最方便的,可能还是使用JS……:P

  18. 老赵
    admin
    链接

    老赵 2006-12-27 20:30:00

    @Kai.Ma
    我觉得您的方法和我这里提到的方法是非常相似的,一个是修改了Form,一个是修改了Page,都是对Render进行了改造……

  19. 老赵
    admin
    链接

    老赵 2006-12-27 20:30:00

    @阿不
    谢谢提醒。:)

  20. 老赵
    admin
    链接

    老赵 2006-12-27 20:31:00

    @Cat Chen
    ASP.NET实在是太灵活了,这个模型搭得真好……

  21. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-27 21:47:00

    @Jeffrey Zhao
    是Search Engine Friendly……

    User Friendly的优先级排在Search Engine Friendly前面,而好的URL同时也是User Friendly的。User Friendly主要包括两点:
    1.容易记忆的URL,例如/{ProductName}比/Product.aspx?ProductId={ProductId}要好。
    2.可删节的,也就是路径中表现出导航层级关系,通过删节URL可以直接实现上上级页面导航。

  22. 老赵
    admin
    链接

    老赵 2006-12-27 21:51:00

    @Cat Chen
    哎,我还是喜欢叫做SEO……而且发现很多资料还是叫SEO哎……
    你说的有道理,虽然我认为,如果是用QueryString表示ID的话,可能有时候会更方便点,一篇文章只要记一个ID就可以了……

  23. 小春[匿名][未注册用户]
    *.*.*.*
    链接

    小春[匿名][未注册用户] 2006-12-28 20:17:00

    宝玉写的一篇关于CS重写Url的日志,特别指出了这个问题。最简单方便的方法就是在web.config中配置<pages pageBaseType=“”/>来替换asp输出的action的地址,

  24. 老赵
    admin
    链接

    老赵 2006-12-28 20:29:00

    @小春[匿名]
    谢谢。:)

  25. Systembug
    *.*.*.*
    链接

    Systembug 2007-01-04 16:47:00

    这样做还是会有问题,Actionless只是去掉了第一次的Action,当你使用Ajax回调多次后,这个还是会出现问题,Action被修改。这个需要在返回Ajax回调结果时在此重写action。

  26. 老赵
    admin
    链接

    老赵 2007-01-04 16:57:00

    @Systembug
    没错,最后一个解决方案的确有问题。

  27. 坏人[未注册用户]
    *.*.*.*
    链接

    坏人[未注册用户] 2007-01-08 10:20:00

    必须用js在Sys.Application.add_load的时候做一些事情,否则点两下后还是会出问题,他会在update后将action改为相对路径,导致错误。

  28. 老赵
    admin
    链接

    老赵 2007-01-08 20:08:00

    @坏人
    其实是:删除action是没有用的,必须用JS处理,或者把action填成Rewrite之前的地址。:)

  29. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-24 10:29:00

    1、这个问题我最近的想法是这样,我不RewriteUrl,因为把请求转到对应的Page,除了Application这里用HttpMoudule,就是Page作为IHttpHandler,在ProcessRequest时候的创建过程,创建时对应正确的页面即可。中间还有没有别的问题没仔细考虑,但定能解决。

    2、SEO就是SEO,不就是难听点吗... 其实Url Friendly并非完全用于搜索引擎,比如用第三方的,或者是未来打算扩展的松耦合的硬盘Cache系统,说来就是生成静态页面,有一个Friendly的Url,比较好处理。

    3、我倒不认为微软的架构有什么棒,设计模式里的东西用的不少,可惜好多地方简直是不懂Web不懂B/S。比如Css Friendly Control Adpter的推出,就说明微软从1.1->2.0,还是没有聆听Web设计的需要,最后不得不补救。仔细看Control Adpter的实现也是一团糟,用于微软最开始设计的目的不错,但是当真正改变控件深入一些的行为模式时,由于限制在更改输出的小范围内,导致行为更改要不不能实现,要不实现的很邋遢,或者干脆有问题,那个Css Friendly 1.0就Bug重重。其实老外的Web应用,一部分人比咱们先进太多,碰到什么问题自然也难不住;其它人说实话水平还不抵国内,导致明明有问题却忽略了,这样微软也得不到正确的反馈。有时候我就想如果开发者自己实现一套轻量化的控件机制,代替Page、UserControl和整个RequestMain的生命周期,可能更适合自己的项目应用。不就是很多过去asp3的工作现在又要做么?其实也没什么,因为我们具有了面向对象的思想不是....

  30. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-24 10:32:00

    我指的创建过程,是指PageHandlerFactory创建Page这个IHttpHandler的过程。

  31. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-24 10:36:00

    对了,如果放弃微软的控件组,还意味着MONO兼容 :) 。可惜Atlas一出,有点舍不得了... Atlas到现在的AJAX 1.0,确实感觉到了微软在变化,变得有可能能靠一家公司抗衡整个Web开源社区了。毕竟,很多工作量大,但并不复杂,照着最好的改进就是了,大量的开源也不过是重复工作,不会因为全世界人民的参加,就比微软做的快做的好很多。只要微软愿意聆听愿意自我否定....

  32. 老赵
    admin
    链接

    老赵 2007-01-24 10:37:00

    @怪怪
    其实ASP.NET完全是支持的。
    ASP.NET定义的模型还是很灵活的,Page/Control只是这个模型的一个实现,我的理解是它们相当于Architecture Patterns和Implementation Patterns的关系。期待您的实现。:)
    // Castle里有个Rails的实现,您有了解吗?

  33. 老赵
    admin
    链接

    老赵 2007-01-24 10:38:00

    @怪怪
    希望有机会能够和您多多交流。:)

  34. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-24 10:52:00

    你是不是24小时在线啊..,我还没睡,刚想去就看见你的回复了。

    这个问题我是这样看,ASP.NET的重头其实还是Page/Control,毕竟没有了这些,你想想System.Web里东西还剩多少...。我的实现肯定只针对一些自己的具体需要啦,对别人不会有什么实用价值,否则量太大。如果大家共同组一个开源项目我倒是愿意参与。

    Castle的Rails还真没了解过,既然你提到了就一定有价值,我会去看看的。这几个有名的项目有过泛泛的关注,但没敢下功夫,因为怕学习了那种很快就被淘汰的对其它工具使用层面上的技能,毕竟我不聪明,学东西要花很多时间。我的过程是先产生需求,再找解决的办法,倒也没因此但无过事;因为理念是我比较在意的,如果找到相符的解决方案掌握起来到一般不会有大障碍。有名的又肯定不能不理,一般只等呼声高到一定程度,才会有与呼声相等了解。比如对Atlas就一直是这样,经常来你这里、PageFlakes的作者那里等几个地方看,自己动作并不多,直到beta1才开始试验性质的用。

    说实话这大半年没少白看你们的文章,好像还有DflyingChen吧,让我省了大劲儿了。对于这里我现在纯粹一个吸血者,等身上压力轻了,我也会来贡献点东西,我的主要风格和技能,就是唱反调,这个我想应该还是很有用,虽然我水平有限 :P。

  35. 怪怪[未注册用户]
    *.*.*.*
    链接

    怪怪[未注册用户] 2007-01-24 11:57:00

    刚才浅浅的了解了一下,感觉MoonRail的未来在于ViewComponent的发展和变化了。没有一个高级的、灵活的、强大的可重用机制,仅只靠引入模版语言和标准MVC的形式,对于ASP.NET而言,有可能不是进步而是倒退。我觉得.NET上应该产生独有的东西,而不是完全实现出一个相似或相同的来。

    多的还需要再仔细瞧,就不班门弄斧了,休息一会儿去了 :)

  36. 老赵
    admin
    链接

    老赵 2007-01-24 12:03:00

    @怪怪
    以后要多来我这里唱反调。:)

  37. 新问题,希望帮忙[未注册用户]
    *.*.*.*
    链接

    新问题,希望帮忙[未注册用户] 2007-07-12 16:15:00

    Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 500

  38. 王振
    *.*.*.*
    链接

    王振 2007-09-03 15:49:00

    我也遇到了楼上的问题
    问题是这样的:
    我在updatepanel中放一个textbox,在textbox中输入"<b>asdf</b>",点击提交就出现Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 500 错误
    我就按你的方法做,可是给我报12031的错误,老赵请帮忙解决

  39. 江大鱼
    *.*.*.*
    链接

    江大鱼 2007-12-21 13:24:00

  40. Sergio[未注册用户]
    *.*.*.*
    链接

    Sergio[未注册用户] 2008-01-11 23:02:00

    Gracias chinito :D!

    Saludos,

  41. 杨鑫[未注册用户]
    *.*.*.*
    链接

    杨鑫[未注册用户] 2008-11-12 06:50:00

  42. wenwenxiaoxing[未注册用户…
    *.*.*.*
    链接

    wenwenxiaoxing[未注册用户] 2009-05-26 15:33:00

    还是解决不了啊!怎么回事,光报错误。说验证视图状态MAC出错

  43. 老赵
    admin
    链接

    老赵 2009-05-26 18:38:00

    @wenwenxiaoxing
    做load balance了?

  44. fangchengshi[未注册用户]
    *.*.*.*
    链接

    fangchengshi[未注册用户] 2009-06-09 16:44:00

    好! 又一认识

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我