asp.net页面处理流程 - 处理 PostBack回来的数据 - LoadAllState 和 ProcessPostData

前面那篇文章说到,如果页面是PostBack回来的话,将会调用 LoadAllState来根据ViewState把前次页面的控件树重新构造起来;
这篇文章详细说一下 LoadAllState 和  ProcessPostData处理流程;

首先,从提交回来的隐藏的ViewState HTML 元素中构造一个 System.Web.UI.Pair 的对象;


估计大家都没有看过页面提交回来的 ViewState的内容吧?老实说,我也没有看过;

今天就看看吧;看过后发现很遗憾,用 base64解码后是byte[]类型,然后用System.Web.UI.ObjectStateFormatter 对其进行反序列化成一个  Pair对象,

也就是说,是二进制数据,无法直接查看;



根据MSDN的说法,提供用于存储两个相关对象的基本实用工具类,里面就只有 First, Second两个属性;

First         如果不为空,则First里面的内容应该填充到控件的ViewState中(Page类对其进行了其他处理),

                 如果为空,则表示该控件不需要设置ViewState;

Second  是空或者一个ArrayLis或者一个Pair (只有当当前元素是Page时,才会是一个Pair)

                为空表示该控件没有子元素的ViewState需要设置;

               不为空,则 该ArrayList的第0个元素用于表示控件序号,第1个元素用于表示该控件的ViewState相应的Pair对象,依次类推;


我试图来直接描述一个Pair吧,

说明:{} 表示一个Pair对象{a=b}表示该Pair对象中,Firs=a,Second=b,[]表示一个ArrayList

{

{"__ControlsRequirePostBackKey__"= ["Repeater1$ctl00$HeadCheckBox","DynamicCheckBox"]}=

        {-20141234=

[1,

          {    null= [1,  { {"Text"="Label1"}=null}  ,  2,  {  {"Text"="Label2"} =null]  

          }

       ]

}

}

上面这个例子中,最终需要如下设置ViewState:

his.Controls[1].Controls[1].ViewState["Text"]="Label1"; 

this.Controls[1].Controls[2].ViewState["Text"]="Label1";


记得我转的那篇文章说Page对于ViewState的处理,在 asp.net 1.1中 原来是两个列表,现在改成一个列表了,就是指的这儿了:

[1,

          {    

null= [1,  {    {"Text"="Label1"}  =null}  ,  2,    {  {"Text"="Label2"} =null]    

         }

 ]


页面将首先对Page对象调用LoadAllState 来加载页面的State,  然后对于Pair的Second中提到的元素 ,调用元素的LoadViewStateRecursive 来加载改元素相应的Pair,

我们的例子中,第一级Pair 将被Pair处理

First 只包含一个 Key:"__ControlsRequirePostBackKey__",  将进行postback的控件的HTML客户端名称(注意,是名称,不是id);

,这个Key的Value是返回一个ArrayList  ["Repeater1$ctl00$HeadCheckBox","DynamicCheckBox"], 

将这个值赋给了page类的一个变量:  _controlsRequiringPostBack;

只有在Page中,对于First进行了特殊处理,在其他控件中,对于First,一般均将其设置控件的到ViewState中,如下面例子中的

 {"Text"="Label1"}  ,为null 则表示不用设置控件的ViewState;

然后处理 Second;  

对于Page实例,Second是一个Pair,它的 First 它的First是前面页面的aspx 类型的HashCode,的值 

首先判断这个HashCode是否和当前页面类型的HashCode是否一致,

如果一致,则调用 LoadViewStateRecursive 来处理 Second的Second来循环加载ViewState;

这意味着,如果你在一个页面显示后,修改了页面的aspx内容(使得Page的控件树发生了改变,例如增加了一个Label,或者调整了一个Label的位置等等),

则提交回来时,提交回来的ViewState将不会进行加载;为什么会这样?后面会进行解释;

对于其他非Page实例,则Second表示了子控件需要设置的ViewState;


Second 里面是什么内容呢?简单来说,以List方式存储了 一个 int 变量,, 和一个 Pair的数组;  

这个int变量表示子控件在父控件中的序号,表示紧跟着的Pair 应该Load到到哪一个控件;

但是,注意,动态创建的控件,控件序号大于等于父控件的子控件数量,

则将先构建一个包含了Hashtable元素的内部对象,将其Index和Pair存到这个 Hashtable元素中;



这也说明了,为啥控件树发生了改变,将不加载ViewState,因为相同的序号在两个页面中对应到完全不同的控件;
页面动态增加的控件,控件序号为非动态控件最大控件序号+1;

我这儿详细描叙以下 Repeater控件根据ViewState重新构建子控件数的过程;
首先,你要有个概念,Repeater控件的ViewState将如同如下形式:
{} 表示一个Pair对象{a=b}表示该Pair对象中,Firs=a,Second=b,[]表示一个ArrayList
{
["_!ItemCount",0]=
{
[    0,
    {
null=
[
  1,
  {
"AutoPostBack",
true
  }
]
    }
]
}
}

上面这个看上去很麻烦,其实很好理解,Repeater的ViewState["_!ItemCount"]应该为0;这个其实用来表示数据项;


然后Repeater下面的Controls[0].Controls[1].ViewState["AutoPostBack"]=true;
然后对于Pair中First,也就是["_!ItemCount",0], 简单将其设置到ViewState中,因为是初次访问Repeater的ViewState,所以将构建一个新的
ViewState对象(StateBag类的实例),调用TrackViewState方法使得ViewState能够跟踪此后ViewState的变化,
然后设置,Repeater的ViewState["_!ItemCount"]=0;
然后处理Pair中的Second,也就是
{
[    0,
    {
null=
[
  1,
  {
"AutoPostBack",
true
  }
]
    }
]

}  ,

调用 LoadChildViewStateByIndex 方法来处理;

首先,这个方法将访问 Controls属性,但此时 Controls属性对应的私有变量为空,所以将先构造一个ControlCollection类的对象,并赋给该私有变量;
然后,在CreateControlHierarchy方法中,发现 ViewState["_!ItemCount"]不为null,因此,Repeater有0个或者x个DateItem,
所以,该Repeaer需要调用CreateItem方法构建一个RepeaerHeade对象;
;


简单来说,就是调用EnsureChildControls方法,该方法中调用CreateChildControls方法,CreateChildControls方法又调用CreateControlHierarchy方法;

但注意 EnsureChildControls方法只会被调用一次;

于是,为Repeater的Header创建了一个 RepeaterItem 元素并将该元素加到Repeater的Controls中,该RepeaterItem 包含有3个子控件,


[LiteralControl,CheckBox,LiteralControl],
对于Repeater的第0个元素(也就是我们刚刚创建的RepeaterItem元素),循环调用该元素的 LoadChildViewStateByIndex  方法来处理子Pair,也就是
{
null=
[
  1,
  {
"AutoPostBack",
true
  }
]
}


附带说一下,在 WebControl.cs的源代码中看到 SB 二字;估计是哪个中国兄弟写的东东。。。。

WebControl.cs 第 173行。。

 if (this.ControlStyleCreated || (this.ViewState["_!SB"] != null))   


然后进行下一个动作:  ProcessPostData 

(处理PostBack回来的数据,带两个参数,一个表示Request带过来的参数,一个为true,表示此时函数调用发生在Load调用之前),


对于Request带过来的参数,进行如下处理,根据name( 客户端名称,不是id)查找控件,
如果找不到控件,将其加到 _leftoverPostData(这个是一个NameValueCollection对象) 变量中;
找到控件,调用该控件的LoadPostData方法来,如果该方法返回 true,表示将触发该控件的某事件,此时,
           _changedPostDataConsumers  中增加该控件,以便在Page_Load方法后触发事件
          如果你点击了Head的CheckBox为选中状态,则PostBack回来后,此时该Head中的CheckBox变为选中状态,并且该控件加到了_changedPostDataConsumers  中;
          同时,还记得上面开始部分我们提到的_controlsRequiringPostBack变量?从 _controlsRequiringPostBack 中删除当前name;


处理完所有Request带过来的参数之后,
然后,对于_controlsRequiringPostBack中剩下来的每一个元素,在页面根据名称查找控件,找到该控件,从_controlsRequiringPostBack中删除;
 
OK,到这个时候,_controlsRequiringPostBack 将只保留将可能会提交数据回来但当前Page中无法找到的元素,也就是你在Page_Load中动态创建的元素了;
至于,其他可能提交数据回来但静态创建的元素(根据aspx构建的控件树中的元素),此步骤被从_controlsRequiringPostBack 中删除掉了;


但很明显,此时还是没有回答我们前面例子页面提出的文题?点击 Head中的CheckBox为啥不触发CheckedChanged事件?

因为_changedPostDataConsumers  中已经添加了该元素了,所以理论上是应该会触发这个事件的;



顺便说一下调试注意事项,如果你也像我一样启动了 asp.net的源代码调试功能的话;

如果你启用了调试状态,并且设置了断点断在Page.cs中LoadAllState方法的时候,

当你去查看 Repeater的Controls,你会发现一个神奇的事情:

不启用调试时,点击Repeater中Head的CheckBox 不会触发 CheckedChanged事件;

启用调试后点击,点击Repeater中Head的CheckBox 能够触发 CheckedChanged事件;

为什么呢?

当初次访问Controls属性时,如果该属性为null,则调用EnsureChildControls来创建ControlCollection的一个实例,并且根据当前该控件的ViewState增加子控件,

但此时Repeater的 ViewState 还没有内容,所以没有任何子控件添加到controls;


在 Page_Load 之前的ProcessPostData  处理时 PostData将因为控件不存在而依旧保留;

这样,在 Page_Load之后的后续 ProcessPostData  处理时,

Repeater绑定后的CheckBox能够从此前的PostData拿到Checked状态,并触发CheckedChanged事件;


看来还得继续往下看。。。不过,估计这两天没有时间了。。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值