asp.net页面处理流程 - 处理 PostBack回来的数据 - LoadAllState 和 ProcessPostData
2013-10-31 01:51
399 查看
前面那篇文章说到,如果页面是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事件;
看来还得继续往下看。。。不过,估计这两天没有时间了。。
这篇文章详细说一下 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事件;
看来还得继续往下看。。。不过,估计这两天没有时间了。。
相关文章推荐
- asp.net页面处理流程 - 2 (Preload, Load, LoadComplete)
- 在ASP.NET 2.0中操作数据之十八:在ASP.NET页面中处理BLL/DAL层的异常
- ASP.NET页面运行机制以及请求处理流程
- ASP.NET 3.5核心编程学习笔记(1):ASP.Net页面请求处理流程
- ASP.NET页面请求处理流程
- 在ASP.NET_2.0中操作数据.在ASP.NET页面中处理BLL.DAL层的异常
- Scott Mitchell 的ASP.NET 2.0数据教程之十八:: 在ASP.NET页面中处理BLL/DAL层的异常
- ASP.NET - 自定义控件处理页面事件(控件与页面数据交互)的方法
- 在ASP.NET 2.0中操作数据之十八:在ASP.NET页面中处理BLL/DAL层的异常
- asp.net 服务器处理页面流程
- asp.net 服务器处理页面流程
- asp.net页面处理流程 - 1
- ASP.NET页面运行机制以及请求处理流程
- Scott Mitchell 的ASP.NET 2.0数据教程之十八:: 在ASP.NET页面中处理BLL/DAL层的异常
- ASP.NET页面运行机制以及请求处理流程
- HttpWebRequest开发向asp.net服务端post数据,对ViewState的处理
- Scott Mitchell 的ASP.NET 2.0数据教程之十八:: 在ASP.NET页面中处理BLL/DAL层的异常
- ASP.Net页面请求处理流程
- ASP.NET页面运行机制以及请求处理流程
- Asp.net的页面处理机制和流程