Hello World
Spiga

UpdatePanel与ASP.NET Page的缓存

2007-02-04 11:18 by 老赵, 7511 visits

UpdatePanel的功能大家一定都非常熟悉了。无论是官方还是社区里热心推广ASP.NET AJAX的朋友,都会对于UpdatePanel的使用进行大量说明与展示。但是在这些简单的的示例似乎都遗漏了一个非常重要的问题,这个问题会直接导致UpdatePanel无法正确使用。

这个问题就是ASP.NET Page的缓存。

ASP.NET Page是个非常强大的模型,缓存是它的重要特性。一个成熟的ASP.NET应用程序几乎都会使用缓存,它能够显著得提高性能,减少服务器端生成页面或者控件内容的消耗。不过现在出现了UpdatePanel这个“神奇”的控件,如果使用缓存不当,就会让我们的应用程序出现错误。

 

重现问题

我们还是来编写一个使用UpdatePanel的简单示例,如下:

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ OutputCache Duration="100" VaryByParam="None" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

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

 

我们打开页面,依次做一下操作,并察看页面上显示的时间。

  1. 多次刷新页面,时间不会改变。
  2. 多次点击Async PostBack按钮,页面部分刷新,时间每次都会改变。
  3. 多次点击PostBack按钮,页面完全刷新,时间只会更新一次,然后时间保持不变。
  4. 点击Async PostBack按钮,发生错误。

直到等待时间超过100秒(Cache过期),点击Async PostBack才工作正常,直到用户再次点击过PostBack按钮。

 

分析问题

使用Fiddler等工具察看出现错误时的Response Body,会发现页面返回了整张页面的内容,而不是UpdatePanel熟悉的特殊的字符串。要说明这个问题,需要再了解一下UpdatePanel的工作方式。

  1. 点击Async PostBack按钮之后,客户端采集页面上所有的信息,使用XMLHttpRequest发起一个请求,并且会在请求的Header信息中设置一个x-microsoftajax的值为Delta=true。
  2. 服务器端的Page页面接受到请求之后,并不会得知这是由XMLHttpRequest发出的请求,因此和常规一样进行页面中所有的工作,例如触发控件事件等等。
  3. 页面上的ScriptManager控件在OnInit时,一旦发现Request的Header里有正确的x-microsoftajax信息,将会意识到目前在进行异步的PostBack。
  4. 如果ScriptManager得知现在正在异步PostBack,则在OnPreRender时调用Pate.SetRenderDelegate方法,提供自定义的方法用于输出页面。
  5. 在自定义的输出方法中,ScriptManager采集所有的信息(例如哪些UpdatePanel进行了更新,需要输出的脚本等等),并且输出客户端能够识别的内容。
  6. 客户端收到信息后,对其进行分析,并且更新页面。

在正常情况下,这个流程应该顺利的跑完,但是如果页面内容在服务器端被缓存了呢?将会出现下面的情况:

  1. 点击Async PostBack按钮,客户端采集页面上所有的信息,使用XMLHttpRequest发起一个请求。
  2. Page在接受到请求之后,发现页面中存在缓存信息、并且缓存没有过期,于是直接输出缓存中的页面内容。
  3. 客户端收到了完整的页面内容,无法识别,抛出异常。

但是为什么在出现一个传统的PostBack之前,异步PostBack都能够正常工作呢?我怀疑这是因为ASP.NET的Page对象是在Render时一并处理缓存的,当ScriptManager替换了Page自带的方法后,就不会对输出内容进行缓存了。这导致了接下来的异步更新发生了错误。这里是我根据出现的状况进行的猜测,哪位朋友如果了解ASP.NET Page缓存的确切情况,请不吝指教。:)

 

解决问题

说实话,如果一个页面已经使用了缓存,我还没有想到一个操作简单,但又能保持其缓存特性的解决方案。另外,我甚至想合理利用这种缓存机制来提高异步更新时的性能(也就是尝试着让页面缓存异步更新的内容)。我查阅了ASP.NET Page缓存的资料,作了一些尝试,但是依旧无法得出一个比较好的解决方案。可能的确是ScriptManager替代页面输出方法造成的问题吧,既然无法在那时处理缓存,又如何能够避免页面缓存的问题,甚至利用缓存来提高性能呢?

如果要让UpdatePanel正常工作的话,只能取消页面级别的缓存了。不过控件级别的缓存还是能够使用的,下面的例子就可以看到控件缓存与UpdatePanel同时工作时的状况:

<%@ Control Language="C#" AutoEventWireup="true" %>
<%@ OutputCache Duration="100" VaryByParam="None" %>
<%= DateTime.Now.ToString() %>
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Register Src="CachedControl.ascx" TagName="CachedControl" TagPrefix="uc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Cached Control</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <uc1:CachedControl ID="CachedControl1" runat="server" />
                <br />
                <%= DateTime.Now.ToString() %>
                <asp:Button ID="Button1" runat="server" Text="Async PostBack" />
            </ContentTemplate>
        </asp:UpdatePanel>
        
        <asp:Button ID="Button2" runat="server" Text="PostBack" />
    </form>
</body>
</html>

 

在这里,我们把缓存加于CachedControl控件上,打开CachedControl.aspx之后并进行多次传统PostBack和异步PostBack都不会出现问题,从页面上两个不同的时间也可以看出控件的缓存生效了。

Creative Commons License

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

Add your comment

44 条回复

  1. Dflying Chen
    *.*.*.*
    链接

    Dflying Chen 2007-02-04 12:21:00

    good,不过没有完善的解决方案阿

  2. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2007-02-04 13:29:00

    ASP.NET 2.0有一个大部分人都不曾用过的控件:Substitution。如果用了Substitution,估计缓存的处理策略就会改变,即使整个页面都被缓存了,只要发现其内部有Substitution就一定要检查Substitution有没有更新。你看看Substitution有没有用吧。

  3. 老赵
    admin
    链接

    老赵 2007-02-04 13:51:00

    @Dflying Chen
    我还没有想到完善的解决方案,目前似乎还是要把Page对象的缓存给去掉。

  4. 老赵
    admin
    链接

    老赵 2007-02-04 13:52:00

    @Cat Chen
    我去看看,我没有系统的学过ASP.NET 2.0,对很多新控件很陌生,呵呵……

  5. 老赵
    admin
    链接

    老赵 2007-02-04 14:14:00

    @Cat Chen
    我看了,Substitution控件没有作用。可以看到每一次异步请求得到的结果虽然不同,但是依旧还是完整的页面内容。
    这也难怪,Substitution的使用方式是绑定一个静态方法,然后每次请求后都会更新Substitution所占的位置。如果是你,你会怎么实现这个功能,又不破坏缓存的功能呢(也就是不去重新生成页面)?
    如果是我的话,我会在第一次生成页面内容时用一个占位符保留Substituion控件的位置,然后放入缓存,当再来一个请求时,我把内容从缓存中取出,然后调用那些静态方法,把得到的字符串替换那些占位符。这样自然不会重新生成页面,但很明显也无法改变现在UpdatePanel的问题。

  6. 老赵
    admin
    链接

    老赵 2007-02-04 16:26:00

    @Dflying Chen
    还有,我有个疑问。
    为什么要在缓存的Page里使用UpdatePanel?好像从条例上或者逻辑上说不太过去,呵呵。

  7. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-04 21:40:00

    期待解决方案.去掉页面缓存不是个好的解决方法.大佬+U

  8. 老赵
    admin
    链接

    老赵 2007-02-04 21:55:00

    @孤叶(学习.net框架)
    不过我没有搞懂,为什么会在存在UpdatePanel的页面中使用Page缓存呢?如果解决一个出现在不合理的状况下的问题,感觉有些不值得……

  9. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-04 22:00:00

    哈!
    这种需求真的有点不合常理
    但如果真的有这种需求呢!
    不知有没有人会遇到!

  10. 老赵
    admin
    链接

    老赵 2007-02-04 22:03:00

    @孤叶(学习.net框架)
    需要有人现身说法一下,呵呵。

  11. 孤叶(学习.net框架)
    *.*.*.*
    链接

    孤叶(学习.net框架) 2007-02-04 22:10:00

    你真的很能发现
    good boy

  12. 老赵
    admin
    链接

    老赵 2007-02-04 22:16:00

    @孤叶(学习.net框架)
    谢谢。:)

  13. 冬冬
    *.*.*.*
    链接

    冬冬 2007-02-05 08:36:00

    我觉得异步的东西本身就是用于动态的应用,没必要缓存吧?

  14. win yee[未注册用户]
    *.*.*.*
    链接

    win yee[未注册用户] 2007-02-05 09:00:00

    页面缓存是针对所有的Session的,如果每个Session的请求内容都不一样,缓存就没有意义了。

  15. 老赵
    admin
    链接

    老赵 2007-02-05 09:09:00

    @冬冬
    缓存控件是有意义的,但是缓存页面对于AJAX页面来说就似乎有些奇怪了……

  16. 老赵
    admin
    链接

    老赵 2007-02-05 09:09:00

    @win yee
    是,缓存是全局的。

  17. qc1984326[未注册用户]
    *.*.*.*
    链接

    qc1984326[未注册用户] 2007-03-08 19:44:00

    用了控件缓存之后,好像还是有点问题的。

  18. 老赵
    admin
    链接

    老赵 2007-03-08 19:57:00

    @qc1984326
    从运行机制上看不应该有问题,您遇到什么问题呢?

  19. qc1984326[未注册用户]
    *.*.*.*
    链接

    qc1984326[未注册用户] 2007-03-08 21:13:00

    我用了控件缓存后还是出现了这么一个提示:
    Sys.WebForms.PageRequestManagerServerErrorException:An unknown error occurred while processing the request on the server.The status code returned from the server was:12030

  20. 老赵
    admin
    链接

    老赵 2007-03-08 21:19:00

    @qc1984326
    这是因为您的代码中抛出了异常,并非因为缓存而在客户端造成了解析错误。
    您可以查看一下您的代码中哪里抛出了异常。

  21. qc1984326[未注册用户]
    *.*.*.*
    链接

    qc1984326[未注册用户] 2007-03-08 21:24:00

    @Jeffrey Zhao
    好的,谢谢,那我再看看先。

  22. qc1984326[未注册用户]
    *.*.*.*
    链接

    qc1984326[未注册用户] 2007-03-08 21:43:00

    @Jeffrey Zhao
    我在原来代码中看了,完全和你上面说的一样;可是一调试就错了。
    我重新建了一个项目,就行了。
    就不知道为啥了。。

  23. yanzimywife[未注册用户]
    *.*.*.*
    链接

    yanzimywife[未注册用户] 2007-03-22 16:01:00

    很喜欢老赵的这种分析问题 解决问题的授人以渔的模式.

  24. 老赵
    admin
    链接

    老赵 2007-03-22 17:48:00

    @yanzimywife
    :)

  25. lonwin[未注册用户]
    *.*.*.*
    链接

    lonwin[未注册用户] 2007-03-24 22:36:00

    @Jeffrey Zhao :
    我也遇到了和@qc1984326同样的问题,已经搞了两天了,还不解决不了

    ---------------------------
    Microsoft Internet Explorer
    ---------------------------
    Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 12030
    ---------------------------
    确定
    ---------------------------

  26. lonwin[未注册用户]
    *.*.*.*
    链接

    lonwin[未注册用户] 2007-03-24 22:39:00

    在aspx文件里面正常,不出现错误,在ascx文件里面就报12030的错

  27. 老赵
    admin
    链接

    老赵 2007-03-24 22:46:00

    @lonwin
    能否给我一个能够跑得简单的例子看一下呢?

  28. lonwin[未注册用户]
    *.*.*.*
    链接

    lonwin[未注册用户] 2007-03-24 23:10:00

    可否告知您的邮箱,我把整个源码发给你看一下吧?剥离出来好象有点困难
    需要VS2005 + SQL SERVER 2000 + ajax.net 1.0的环境!

    或者加我MSN:lonwin@zzlover.com ,感激不尽,已经搞了几天了,也没结果
    偶尔会报404或者12031的错误!

    可以参考下面这篇文章:
    http://www.cnblogs.com/frogbag/archive/2007/03/16/676768.html

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

    lonwin[未注册用户] 2007-03-24 23:14:00

    @Jeffrey Zhao

    CompanyAdmin.ascx:
    <%@ Control Language="c#" %>

    <form id="Form1" runat="server">
    <div class="contentTitle">经营单位管理</div>

    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
    <asp:GridView SkinID="GridView" ID="GridView1" DataKeyNames="id" PageSize="18" runat="server" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="False" DataSourceID="SqlDataSource1">
    <Columns>
    <asp:BoundField DataField="id" HeaderText="ID" InsertVisible="False" SortExpression="id" />
    <asp:BoundField DataField="companyId" HeaderText="海关编号" ReadOnly="True" SortExpression="companyId" />
    <asp:BoundField DataField="companyName" HeaderText="经营单位名称" SortExpression="companyName" />
    <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
    </Columns>
    </asp:GridView>
    </ContentTemplate>
    </asp:UpdatePanel>

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:CustomsConnStr %>"
    DeleteCommand="DELETE FROM [company] WHERE [id] = @id" InsertCommand="INSERT INTO [company] ([companyId], [companyName]) VALUES (@companyId, @companyName)"
    SelectCommand="SELECT * FROM [company] ORDER BY [id] DESC"
    UpdateCommand="UPDATE [company] SET [companyId] = @companyId, [companyName] = @companyName WHERE [id] = @id">
    <DeleteParameters>
    <asp:Parameter Name="id" Type="String" />
    </DeleteParameters>
    <UpdateParameters>
    <asp:Parameter Name="id" Type="Int32" />
    <asp:Parameter Name="companyName" Type="String" />
    <asp:Parameter Name="companyId" Type="String" />
    </UpdateParameters>
    <InsertParameters>
    <asp:Parameter Name="companyId" Type="String" />
    <asp:Parameter Name="companyName" Type="String" />
    </InsertParameters>
    </asp:SqlDataSource>
    </form>

    <RewriterRule>
    <LookFor>~/(Default|User|Admin|bbs|JScript)/([,_A-Za-z0-9]+)\.htm</LookFor>
    <SendTo><![CDATA[~/$1.aspx?g=$2]]></SendTo>
    </RewriterRule>

    浏览器中的地址为:
    http://localhost:4000/CustomsApp/User/CompanyAdmin.htm

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

    lonwin[未注册用户] 2007-03-24 23:19:00

    比如分页一共有5页,点击第二页和第三页的时候没问题,点击第四页肯定就报错!不是是怎样?页面刷新后第三次响应事件就报错!

  31. 老赵
    admin
    链接

    老赵 2007-03-24 23:32:00

    @lonwin
    IM & Email:jeffz@live.com

  32. ringking[未注册用户]
    *.*.*.*
    链接

    ringking[未注册用户] 2007-05-17 11:22:00

    @Jeffrey Zhao :
    遇到了和@lonwin同样的问题,做的是最简单的ajax例子,但还是抛这个异常,不知道是为什么,郁闷中。。。。。
    ***************************
    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
    <title>Untitled Page</title>
    </head>
    <body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <div style="text-align: center">
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
    <asp:Label ID="Label1" runat="server" Text="Label" Width="128px"></asp:Label>
    <br />
    <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
    </ContentTemplate>
    </asp:UpdatePanel>
    </div>
    </form>
    </body>
    </html>
    **********************************
    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;

    public partial class _Default : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void Button1_Click(object sender, EventArgs e)
    {
    this.Label1.Text = "***********";
    }
    }
    ***********************************
    求教!!!!!~~~~~~

  33. 老赵
    admin
    链接

    老赵 2007-05-17 19:49:00

    @ringking
    我有时间的时候看一下,您说的是什么异常?

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

    ringking[未注册用户] 2007-05-18 09:47:00

    @Jeffrey Zhao :

    谢谢,赵大哥。

    我没有写什么代码,就是拖了个UpdatePanel,在里面加了一个lable,一个button,就这样都报异常。不知道为什么.......

    建站的时候,也是ASP.NET AJAX-Enabled Web Site,
    也有ScriptManager,但是抛异常

    web页面抛的异常

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

    请指教,谢谢。

  35. 老赵
    admin
    链接

    老赵 2007-05-18 13:06:00

    @ringking
    您的开发环境,操作系统之类的是什么样的阿?

  36. ringking[未注册用户]
    *.*.*.*
    链接

    ringking[未注册用户] 2007-05-20 12:43:00

    @Jeffrey Zhao :

    Visual Studio 2005

    xp

  37. 老赵
    admin
    链接

    老赵 2007-05-21 13:19:00

    @ringking
    Web.config有没有配置好呢?

  38. ringking[未注册用户]
    *.*.*.*
    链接

    ringking[未注册用户] 2007-05-26 09:22:00

    @Jeffrey Zhao :

    还要手动配置什么吗???

  39. 狂人
    *.*.*.*
    链接

    狂人 2007-05-26 21:13:00

    12030是微软的内部错误编号,在这里有解释:
    http://support.microsoft.com/default.aspx?scid=kb;EN-US;193625

    ERROR_INTERNET_CONNECTION_ABORTED
    The connection with the server has been terminated.

    应该不是代码的问题,而是Web Development Server或者IIS的配置问题

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

    ringking[未注册用户] 2007-05-28 10:30:00

    @狂人

    Web Development Server 应该怎么配置啊?
    我找不到相关的资料。

    请教~~

  41. bluesky (darkblue@petrochina.com.cn)[未注册用户…
    *.*.*.*
    链接

    bluesky (darkblue@petrochina.com.cn)[未注册用户] 2007-10-16 09:38:00

    我分析了以下,应该很正常。缓存是在第一次刷新时建立的。以后的刷新会导致Cache生效,而不会返回AJAX Response。可以使用varybyparm="*" 这样在进行异步刷新时,由于Header中x-microsoftjava发生变化,导致缓存失效而重新处理请求。

  42. bluesky (darkblue@petrochina.com.cn)[未注册用户…
    *.*.*.*
    链接

    bluesky (darkblue@petrochina.com.cn)[未注册用户] 2007-10-16 09:48:00

    另外:参考CACHE 说明,可以利用CACHE API 采用varyByheader方法进行缓存。根据说明可以,我没有试验。有兴趣的可以试验一下。varyByHeader设置为x-microsoftajax。:)

  43. 老赵
    admin
    链接

    老赵 2007-10-16 15:55:00

    @bluesky (darkblue@petrochina.com.cn)
    谢谢您的提醒,我会注意的。:)

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

    uie[未注册用户] 2009-04-18 22:16:00

    好东西,慢慢研究!

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我