尤其要感谢xiaosuo、爱问天、徐少侠 等同学的回复,使得我发现了我以往的几个错误认识。重新学习过后,我们开始我们的第二讲的学习研究吧!这一次,我们关注的焦点是:
1. 什么是VIEWSTATE, 什么不是VIEWSTATE ?
2. VIEWSTATE和页面生命周期的关系 ?
VIEWSTATE首先表现为控件(Control)的属性,类型为StateBag。而StateBag实现了IDictionary(所以是一种键值对)和IStateManager。而IStateManager定义了三个方法:LoadViewState、SaveViewState和TrackViewState,一个属性:IsTrackingViewState(再次推荐.NET Reflactor反编译工具,还没有的同学赶快下一个,好东西呀)。望文生义,可以大概猜测得到,VIEWSTATE应该会被加载(Load),被保存(Save);而Track是什么意思呢?
ASP.NET页面生命周期是一个比较复杂的东西,而和理解VIEWSTATE有关的呢,应该是以下几个:Init、LoadViewState、LoadPostBackData、Load、RaisePostBackEvent和Render几个阶段。下面简要的说明一下:
1. Init:初始化控件。将页面初始化为一个“控件树”(和DOM树其实很相似),一般来说,根节点就是一个HtmlForm控件,就是由<form runat="server" id="form1"></form>转化而来的。
2. LoadViewState:这个阶段,有页面Post过来的VIEWSTATE将被解析加载(其实解析和加载还可分为两步,此处不细究)到控件。使得控件被回复到页面被提交之前的状态。
3. LoadPostBackData:这个阶段和VIEWSTATE没有关系,但能澄清我们很多人的误解!包括我第一节里所犯的最大的错误,为了更清晰的演示,我修改了之前的代码:
< form id = " form1 " runat = " server " >
< div >
< asp:Label ID = " Label1 " runat = " server " Text = " I am Label " ></ asp:Label >< br />
< asp:TextBox ID = " TextBox1 " runat = " server " Text = " I am TextBox " ></ asp:TextBox >< br />
< asp:DropDownList ID = " DropDownList1 " runat = " server " >
< asp:ListItem Text = " I am ListItem (1) " ></ asp:ListItem >
< asp:ListItem Text = " I am ListItem (3) " ></ asp:ListItem >
< asp:ListItem Text = " I am ListItem (2) " ></ asp:ListItem >
</ asp:DropDownList >< br />
< asp:LinkButton ID = " LinkButton1 " runat = " server " Text = " I am LinkButton " > LinkButton </ asp:LinkButton >< br />
< asp:Button ID = " Button1 " runat = " server " Text = " I am Button "
onclick = " Button1_Click " />< br />
< asp:HyperLink ID = " HyperLink1 " runat = " server " NavigateUrl = " ~/Default2.aspx " > HyperLink </ asp:HyperLink >
</ div >
</ form >
</ body >
{
Response.Write(Request.Form);
Label l = new Label();
l.ID = " lblAdded " ;
l.Text = " I am added dynamicly " ;
this .form1.Controls.Add(l);
}
protected void Button1_Click( object sender, EventArgs e)
{
this .Label1.Text = " changed " ;
this .TextBox1.BorderStyle = BorderStyle.Solid;
this .TextBox1.BackColor = Color.Blue;
this .TextBox1.BorderWidth = 2 ;
Label l = this .form1.FindControl( " lblAdded " ) as Label;
l.Text = " changed " ;
}
按照第一节里的方法运行代码,我们可以发现,禁用VIEWSTATE之后,TextBox里的“值”是仍然可以保存的,但TextBox的边框熟悉不能被保存!所以,可以最直观的回答“什么是VIEWSTATE, 什么不是VIEWSTATE”:TextBox里的“值”不是VIEWSTATE,而TextBox的边框属性就是VIEWSTATE!
更深一层,我们会发现,TextBox实现了IPostBackDataHandler,而IPostBackDataHandler定义了两个方法:LoadPostData()和RaisePostDataChangedEvent()。TextBox(包括所有实现了IPostBackDataHandler的控件)的“值”是由以上这两个方法实现的,而不是VIEWSTATE,也不是我之前认为的浏览器。
我们可以通过设置断点,或者通过其他http工具,获得当页面回复时,Request.Form里面的数据(这是在禁用VIEWSTATE的情况下收集的):
可以看到TextBox1、DropDownList1和Button1其实是被传送回服务器端的(其实想想这好像是废话,呵呵)。不过注意他们是通过“正常的”<form action=……></form>表单,submit提交的!而不是像VIEWSTATE这样通过hiddenInput提交的。所以他们当然有理由分别处理了!
4. Load:这个就是我们最熟悉的阶段了,呵呵。到此阶段,所有的控件属性都已经被加载完毕(通过LoadViewState和LoadPostData)。
5. RaisePostBackEvent:这个阶段其实就是处理我们自己订阅的事件的,典型的如Button_Click。在这个阶段,我们就可能更改控件的状态。这种更改,就应该被记录下来(如果需要的话),而记录这种更改的,正是下面的SaveViewState。
6. SaveViewState: 在这里,将需要记录保存的VIEWSTATE编码(不是加密)成一个字符串。(是否服务器端会保存一个副本呢?来回传输的是页面全部的控件状态呢,还是变化的?)。
7. Render:更新过的VIEWSTATE被包含在 hidden input 里,发送到客户端。
以上就是VIEWSTATE生成的过程。
但是,如果回头再仔细想一想的话,我们还可能会产生下面的一些疑问:
是不是所有的控件信息都会被保存为VIEWSTATE?有这个必要么?肯定没有!
比如在页面上声明的控件属性,
每一次页面加载,都会使用页面声明的属性来解析、初始化页面,何必需要VIEWSTATE呢?所以VIEWSTATE根本就不会存储这种信息,它存储的是通过后台程序(cs文件)动态修改的属性,这当然也就包括了通过编程添加的控件。
l.ID = " lblAdded " ;
l.Text = " I am added dynamicly " ;
this .form1.Controls.Add(l);
这也是gridview的VIEWSTATE如此巨大的一个重要原因,GridView里面的子控件全部都是动态生成的呀!
而更进一步,前面我们说过,VIEWSTATE必须实现三个方法:LoadViewState、SaveViewState和TrackViewState。LoadViewState和SaveViewState都已在相应的页面事件中实现了(Page其实是一个template controls,它会迭代的调用它的子控件中的LoadViewState和SaveViewState方法),所以剩下的就是TrackViewState方法了。该方法在init之后被调用,它将确保所有在此之前(Init完成之前)所获得的控件状态不会被Save!换句话说,VIEWSTATE只记录页面Init之后的控件状态。(你可能会想,那我何不在Init的时候生成GridView,是不是就会大量的减少VIEWSTATA?呵呵,留做思考题吧,我也想想,应该是不可行的。)一个很好的参考资料: Understanding ASP.NET View State这篇文章涵盖了本文的所有内容,还包含VIEWSTATE的解析、加密和压缩的内容。而且有详细的图片解说,相当值得一看。