2006年11月19日星期日

深入理解 ASP.NET 动态控件 (Part 3 - 页面生命周期)

前言

在上一篇文章中,承诺了这一篇开始讲解释器的,不过看来要按着一个大框架来写文章不那么容易,没仔细推研究过就写出来的内容似乎很应付式。所以我决定恢复我原来的写作习惯,我觉得哪部分的内容已经成熟了,那就把它release出来,没成熟的就继续留在我的draft里面。这次要讲的是页面生命周期,动态控件对此关注的当然是动态与静态控件在生命周期中加载的差别。

一般加载

虽然一般加载过程已经被说过很多次了,但我在这里还要说,希望能把每一个阶段的特点描绘出来,让大家加深印象。

一般加载分为以下几个主要阶段(粗体标出的阶段的特殊性后面解释):

  1. Init - 初始化,是否为动态控件就以此为分界,Init之前加入到控件树的控件其处理过程就和ASPX中静态声明的一致,因为静态控件也就是在Init前加入的。
  2. LoadViewState - 加载ViewState。
  3. ProcessPostData - 处理PostData,倒不如说是加载PostData,因为此阶段控件多数仅加载PostData,顺便判断PostData是否有改变,别的处理不在此阶段作。
  4. Load - 加载,让ASP.NET程序员尽情发挥创意的地方,包括如何糟蹋ASP.NET这个框架。
  5. ProcessPostData Second Try - 第二次尝试处理PostData,和第一次所做的一样,不过第一次执行时已在控件树上的控件不会受到第二次打扰。
  6. Raise ChangedEvents - 冒泡Changed类事件,这里指的是由于PostData变更而引起的Changed类事件。
  7. Raise PostBackEvent - 冒泡PostBack类事件,除了Changed类以外的所有事件都在这里引发。
  8. PreRender - 预呈现,这名字不怎么好记,改为“末日审判”或许会好一些,因为作为上帝的程序员在这里判决每一个变量的最终值。
  9. SaveViewState - 保存ViewState,判决执行的阶段,变量最终值在此保存,判入地狱的变量无权进入ViewState这个天堂并从此消失。
  10. Render - 呈现,可能是生命周期中最无法解耦的一个阶段。
  11. Unload - 卸载,有加载自然有卸载,但其实没有多少人知道它的存在。

这11个主要阶段可以简单分为3大步骤:

  1. 加载数据:LoadViewState, ProcessPostData, ProcessPostData Second Try
  2. 处理数据:Raise ChangedEvents, Raise PostBackEvent
  3. 保存数据:SaveViewState

这3大步骤构成了ASP.NET页面处理体系,其中第2步的处理数据是基于事件冒泡的形式,也正是ASP.NET比ASP先进的地方。ASP.NET把是否处理以及如何处理分离开来了:控件内部的逻辑决定是否处理,如果要处理就触发事件;控件外部的逻辑决定如何处理,仅当事件触发时才会被执行。

追赶加载

与其说动态加载,不如说追赶加载,因为动态加载的过程包含追赶加载,这是和静态加载的主要区别。每一个控件内部都保存着它当前的加载进度,也就是它到达了上述的哪一个阶段,当我们执行Control.Controls.Add方法来将一个控件添加到另一个控件中时,父控件就会检查子控件的加载进度,如果子控件的加载进度比自己的慢了,就会要求子控件追赶上来,所以叫做追赶加载。

在上面11个主要阶段中,用粗体标出的阶段就是追赶加载时必须补回执行的阶段,而其他则是追赶加载时错过了就忽略的阶段。正是由于有一些阶段不被包括在追赶加载中,所以如果我们的控件要使用到这些阶段,就必须保证在这些阶段之前加载。也就是说,如果控件要处理PostData,包括加载PostData及根据PostData触发事件,则必须赶上ProcessPostData Second Try,这意味着它必须在Load的时候加载。否则一旦错过ProcessPostData Second Try,一个控件将在PostBack中表现得和非PostBack时一样,完全不知道有PostData这回事。

结论

其实结论已经说了,在此再强调一遍:如果你的控件要能成功触发事件,必须在Load阶段加载,如果在Load阶段之后(例如另一个控件的事件中)加载,那么此控件的事件无法正常触发。

问题与实验

先解答上次的问题与实验:

  1. this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));是正确的做法。ASCX与ASPX的编译方式是类似的,MyUserControl类只是一个中间过程,仅包含C#代码的编译结果,不包含ASCX的逻辑。而使用Page.LoadControl方法获得的类才是一个UserControl的最终编译结果,包含了ASCX的逻辑。
  2. 这个实验我自己也没去做过,有兴趣的朋友可以自己做一下看看结果如何。

然后是本次的问题与实验。

  1. 如果要求页面上有一个Button,点击后出现一个CheckBox,要这个CheckBox能够正常触发CheckChanged事件,应该怎么做?注意,不要使用隐藏控件的方法,因为隐藏控件所生成的HTML和ViewState是要占用空间的,我希望这个CheckBox在Button被点击之后才在页面生命周期里出现。
  2. 为什么ICallbackHandler在Beta2中仅有RaiseCallbackEvent一个事件,而到了正式版中被拆分为RaiseCallbackEvent和GetCallbackResult两个事件?(提示:这和页面生命周期的阶段划分有关)

如果想讨论或者公布答案的,可以直接在文章评论中进行。如果想知道详细的分析,敬请期待本系列文章的下一篇,通过订阅Cat in dotNET将确保你不会错过本系列的后继文章。

没有评论:

发表评论