这两天看了思归的动态控件状态问题相关文章,通过分析系统类库源码,对控件的生存周期和ViewState的运行细节有了更深一层的认识。
      思归文章里主要讨论的问题:下面两页差别很小,就是一句语句的前后次序有所不同,但PostBack后显示效果有所不同,请解释为什么显示效果不同。

TestDyn1.aspx:

<html>
<body>
 <form id="form1" runat="server">
  <asp:Button id="btn" runat="server" Text="Click Me" OnClick="Button_Click" /> <br/>
  静态: <asp:DropDownList id="ddlStatic" runat="server">
  <asp:ListItem Text="1" Value="1" />
  <asp:ListItem Text="2" Value="2" />
  <asp:ListItem Text="3" Value="3" />
       </asp:DropDownList> <br/>
  动态:
 </form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs e)
{
   DropDownList ddlDynamic = new DropDownList();
   ddlDynamic.ID = "ddlDynamic";

   form1.Controls.Add(ddlDynamic);

   if (!IsPostBack)
   {
 for (int i=1; i <=3; i++)
  ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));
   }

  
 
   if (IsPostBack)
   {
    Response.Write("[Page_Load]静态:" + ddlStatic.SelectedIndex + "<BR>");
 Response.Write("[Page_Load]动态:" + ddlDynamic.SelectedIndex + "<BR>");
   }
}

void Button_Click(Object sender, EventArgs e)
{
 DropDownList ddlDynamic = (DropDownList)form1.FindControl("ddlDynamic");
      Response.Write("[Button_Click]静态:" + ddlStatic.SelectedIndex + "<BR>");
 Response.Write("[Button_Click]动态:" + ddlDynamic.SelectedIndex + "<BR>");
}
</script>

TestDyn2.aspx:

<html>
<body>
 <form id="form1" runat="server">
  <asp:Button id="btn" runat="server" Text="Click Me" OnClick="Button_Click" /> <br/>
  静态: <asp:DropDownList id="ddlStatic" runat="server">
  <asp:ListItem Text="1" Value="1" />
  <asp:ListItem Text="2" Value="2" />
  <asp:ListItem Text="3" Value="3" />
       </asp:DropDownList> <br/>
  动态:
 </form>
</body>
</html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs e)
{
   DropDownList ddlDynamic = new DropDownList();
   ddlDynamic.ID = "ddlDynamic";

   if (!IsPostBack)
   {
 for (int i=1; i <=3; i++)
  ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));
   }


   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)
{
 DropDownList ddlDynamic = (DropDownList)form1.FindControl("ddlDynamic");
      Response.Write("[Button_Click]静态:" + ddlStatic.SelectedIndex + "<BR>");
 Response.Write("[Button_Click]动态:" + ddlDynamic.SelectedIndex + "<BR>");
}
</script>

      很多网友对问题进行了讨论,最后思归在动态控件状态问题(续)中给出了正确答案,大体意思是:动态控件是在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,我们看系统源码:

public void Add(ListItem item)
{
    
this.listItems.Add(item);
    
if (this.marked)
    
{
        item.Dirty 
= true;
    }

}

下面是Item的属性Dirty的定义:

internal bool 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方法,代码如下:

internal object 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(nullthis.Value);
    }

    
return null;
}

在这里展现出了misc字段的作用,也是整个问题的关键,如果不是之前执行form1.Controls.Add()触发了ListItemCollection.TrackViewState将misc.Get(2)和misc.Get(3)置为true的话,这里将返回null值,亦即ddlDynamic的所有Items的视图状态没有保存。到这里TestDyn2.aspx输出的原因已经一目了然了。

posted on 2007-03-08 14:39  风生水起  阅读(4405)  评论(14编辑  收藏  举报