2006年4月3日星期一

LoadPostData 最多只有两次

在很多MSDN的一般文档中,都回避了LoadPostData这个问题,直接就说Page由Init到Load然后就是控件事件了。而根据我的实验,在Load的前后会出现两次的LoadPostData,而最后在MSDN也找到了独立的文章证实了这个流程。

一般的HTML中静态声明的控件初始化应该发生在Init,Init之后这些控件也就初始化完成。然后此时会有一次LoadPostData,这是面向仅使用HTML静态声明控件的情况,这样在Load的时候这些静态声明的控件就已经成功加载了数据,可以直接访问这些数据做一些事情了(例如根据这些数据动态创建控件)。在Load之后,如果确实有动态创建了控件,那么第二次LoadPostData就会被调用,用于为这些动态创建的控件加载数据,如果在上一次页面生命周期这些控件也有被动态创建,那么此时它们的数据就得以加载到本次页面生命周期的对应控件实例。再之后就是控件事件阶段了,所有控件都根据自己加载回来的数据判断是否要引发什么事件。

动态创建控件的最迟不能晚于第二次LoadPostData,否则创建的控件在下一个生命周期就算再次创建同样的控件也不能成功加载数据,这就确定了动态创建控件不能放到控件事件中进行,因为控件事件发生在LoadPostData后。(理论上页面生命周期可以设计为递归型的,也就是每次引发控件事件后再检查是否有添加新控件,有的话再LoadPostData,LoadPostData后再检查这些控件是否需要引发事件,不过实际上页面生命周期没有这样设计。)基本上动态创建控件必须在Load前后完成,例如开发复合控件或者模板控件时常在CreateChildControls和DataBinding中做此项工作。

如果涉及复杂的控件创建,这些控件创建必须依赖于事件改怎么办?这就只有一个办法,把动态创建变成创建所有并动态隐藏。例如制作一个有很复杂规则的调查问卷,根据答题者不同的选择转跳来转跳去(这时候你可能首先会想到ASP.NET 2.0的Wizard控件或者CrossPagePostBack,但这样每次仅显示当前的一个问题),先在页面上创建所有题目并且在Load时根据当前答题情况隐藏不应该出现的问题就是一个解决方案。这里隐藏的关键,就在于隐藏的控件不呈现到HTML上(或呈现到HTML但隐藏),但它们的ViewState却照样保存,这就使得所有的题目实际上都能够跨越页面生命周期而存在并且实现数据持久。

不过Page级别的隐藏方法好像不那么美观,所以我们应该这些逻辑封装到一个WebControl里。既然封装到WebControl里,也就没必要让各个隐藏状态的控件自行保存ViewState,直接把它们的属性统一集中到父控件并保存到其ViewState就行了。此时所有子控件的数据加载变成了由父控件统一控制,当一个子控件不需要出现时就不再需要先创建再隐藏而根本就不用创建,因为父WebControl会继续负责此子控件数据的持久。

其实ASP.NET 2.0的Wizard可能也是用类似的方式实现。在ASP.NET 1.1时我就提出过Wizard,觉得这是一个非常容易实现的控件(就按照上面的父控件统一实现子控件数据持久的方法),而且也很有价值,就是奇怪为什么各大WebControl开发商不做一个出来,结果到ASP.NET 2.0官方就做了一个出来了。至于CrossPagePostBack,用的也就是没有控件但ViewState保留的做法,每次CrossPagePostBack都把PreviousPage的整个ViewState保留下来,不过唯一问题就是如果连续用很多个CrossPagePostBack实现一个Wizard那么多个ViewState堆叠下来占用的传输带宽也一定不少。

所以说,在ASP.NET里面用好ViewState很关键(这个下次详细说),如果还是没ViewState的思想,还是按照其它动态页面语言的思路去做,那是用不好ASP.NET的控件模型的。

1 条评论:

  1. 非常好的文章:)
    正好苦恼于究竟LoadPostData什么时候被调用,发现在Init和Load方法中动态添加控件不一样。而微软有的文章竟然说在调用Controls.Add(control)时自动调用control的LoadPostData方法,却无法用代码检验。看来的确要等到Load方法过后才调用,实际的代码运行结果也是如此。

    回复删除