这两天看了思归的动态控件状态问题相关文章,通过分析系统类库源码,对控件的生存周期和ViewState的运行细节有了更深一层的认识。
思归文章里主要讨论的问题:下面两页差别很小,就是一句语句的前后次序有所不同,但PostBack后显示效果有所不同,请解释为什么显示效果不同。
TestDyn1.aspx:
<html> form1.Controls.Add(ddlDynamic); if (!IsPostBack) void Button_Click(Object sender, EventArgs e) |
TestDyn2.aspx:
<html> if (!IsPostBack) form1.Controls.Add(ddlDynamic); if (IsPostBack) { Response.Write("[Page_Load]静态:" + ddlStatic.SelectedIndex + "<BR>"); Response.Write("[Page_Load]动态:" + ddlDynamic.SelectedIndex + "<BR>"); } } void Button_Click(Object sender, EventArgs e) |
很多网友对问题进行了讨论,最后思归在动态控件状态问题(续)中给出了正确答案,大体意思是:动态控件是在Page_Load中新建的对象,处于原始状态,当我们在Page_Load里调用form1.Controls.Add()时,父控件form1处于Load阶段,它就会调用下拉框的一些方法让它经过Init->Load状态,其中的一个结果是在Init后面调用了TrackViewState,DropDownList的父类ListControl,override了TrackViewState,在其中调用了Items(ListItemCollection类)对象的TrackViewState。其结果是,如果你在form1.Controls.Add()之后改变动态DropDownList控件的Items的话,那些ListItem就会被保存下来,因为ListItemCollection对象 override 了 SaveViewState() 。而在form1.Controls.Add()之前添加的ListItem则不会被保存下来。
根据思归的解释,大体明白了问题的原因,不过对于中间流程依然很模糊,不明白TrackViewState是干嘛的,也不明白Begin Tracking View State阶段都做了什么?为什么form1.Controls.Add()之前添加的ListItem不会被保存下来?上网查了一下TrackViewState在MSDN上的解释是:
Control.TrackViewState :导致跟踪服务器控件的视图状态的更改,以便这些更改可以存储到服务器控件的 StateBag 对象中;
IStateManager.TrackViewState:当由类实现时,指示服务器控件跟踪其视图状态更改;
Calendar.TrackViewState :标记开始跟踪的起始点,并将对控件所做的更改作为控件视图状态的一部分进行保存。
查看一下具体源码,基本上TrackViewState是将控件的marked字段置为true;
现在来看思归提出的问题,在TestDyn1.aspx中,当执行form1.Controls.Add()时,会经历如下过程,调用到ListItemCollection.TrackViewState,将置ListItemCollection.marked为true。:
Void System.Web.UI.ControlCollection.Add(Class System.Web.UI.Control)
Void System.Web.UI.Control.AddedControl(Class System.Web.UI.Control,I4)
Void System.Web.UI.Control.InitRecursive(Class System.Web.UI.Control)
Void System.Web.UI.WebControls.ListControl.TrackViewState()
Void System.Web.UI.WebControls.ListItemCollection.TrackViewState()
然后程序执行ddlDynamic.Items.Add,我们看系统源码:
{
this.listItems.Add(item);
if (this.marked)
{
item.Dirty = true;
}
}
下面是Item的属性Dirty的定义:
{
get
{
if (!this.misc.Get(2))
{
return this.misc.Get(3);
}
return true;
}
set
{
this.misc.Set(2, value);
this.misc.Set(3, value);
}
}
通过item.Dirty = true这句,将导致Item的BItArray类型的私有字段misc中key值为2,3的value为true。
当页面执行到SaveViewState阶段,在这一阶段中将会获取所有控件以及页面本身的 ViewState 集合的内容,所得到的视图状态随后序列化、进行哈希运算、进行 Base64 编码并关联到 __VIEWSTATE 隐藏字段。System.Web.UI.WebControls.ListItem重写了SaveViewState方法,代码如下:
{
if (this.misc.Get(2) && this.misc.Get(3))
{
return new Pair(this.Text, this.Value);
}
if (this.misc.Get(2))
{
return this.Text;
}
if (this.misc.Get(3))
{
return new Pair(null, this.Value);
}
return null;
}
在这里展现出了misc字段的作用,也是整个问题的关键,如果不是之前执行form1.Controls.Add()触发了ListItemCollection.TrackViewState将misc.Get(2)和misc.Get(3)置为true的话,这里将返回null值,亦即ddlDynamic的所有Items的视图状态没有保存。到这里TestDyn2.aspx输出的原因已经一目了然了。