value="/wEPDwUKMTEyNDAzMzYwMw9kFgICAw9kFgICBQ8PFgIeBFRleHQFATBkZGQ+Un61OAiXnW7Zrz37EiJnbfsxfg=="/>
这是一个“隐藏域”的“Html”标记。
它的值是被加密了的字符串,主要是记录下控件或页面的属性取值。
在每次页面请求的过程中都要被自动恢复到控件上去,这样就能把“无状态”的页面请求变成所谓的“有状态”的页面请求。
在Page和每个控件的类定义中,都有一个受保护的成员属性“ViewState”用来记录它们的属性值,并在每次页请求的初期从“ViewState”中恢复上次页请求时记录下来的取值到页面或控件的属性上,以备当前请求之用。
而页面上的很多控件都具有与用户交互的特性,例如:
文本框控件能够接受用户的输入,并将其回发给服务器。
但同时,很多控件的属性在设计时可以指定一个初始值,这样就产生了两种视图状态:
静态页视图状态;动态页视图状态。
(1)静态页视图状态
控件的属性在设计时给定的取值。
这些取值在页面被初始化之前就被赋值给控件的属性。
(2)动态页视图状态
控件的属性在运行时由用户从UI输入的值,或者用户在后台代码中给与的赋值。
对于前者来说,用户输入的值会在页面初始化之后,页面装载之前被自动恢复给控件的属性。
而对于后者来说,在页面卸载之前还可以通过赋值改变控件的属性值,以呈现到浏览器中,而在卸载阶段赋值,则无法将改变之后的属性值呈现给浏览器。
3.3.页请求的类别
对于同一个页的请求分为两种:
初次请求;回发请求。
对于页和控件,都有一个公共属性IsPostBack,用来标识页请求的类别。
(1)初次请求(FirstRequest)
页面第一次加载到浏览器中,为初次请求。
一般地,用户通过在浏览器的地址栏内输入页面Url地址发出的请求,通过用户点击超文本链接而转到该页面的请求都是初次请求。
可以通过判断“IsPostBack==false”来确定当前页面的请求为初次请求。
(2)回发请求(PostBackRequest)
当用户与页面上的控件进行交互而引起的当前页面刷新,将当前页面回送给服务器,再次对该页面的请求为回发请求。
可以通过判断“IsPostBack==true”来确定当前页面的请求为回发请求。
例如:
Button控件的Click事件,是由用户单击Button控件引发的,这就会引起回发请求。
4.页生命周期
从程序代码的角度上看,每个页面实际上是一个类,每次页请求,就会进行一次类的实例化,以及按照特定顺序和规则调用其定义的事件方法的过程。
所以每次页请求的时候,控件属性都会恢复到初始值上去,这也是从代码角度看到的“无状态”的情况。
而这一过程也是我们称之为的“页生命周期”概念。
那么到底一个页面可以定义多少个事件方法?
并且它们是按照什么顺序和规则被调用的呢?
图5详细描述了页面和控件所具有的所有事件以及调用顺序。
图5页面与控件事件及调用顺序
可以看出页的事件要比控件的事件多,更加丰富。
需要注意:
①控件的Init事件发生在页的Init事件之前;②控件的Load事件发生在页的Load事件之后;③控件的PreRender事件发生在页的PreRender事件之后;④控件的Unload事件发生在页的Unload事件之前。
⑤页和控件的回发请求的事件在各个控件的Load之后发生,如:
Button控件的Click事件;⑥在页和控件的回发请求事件调用之前,会进行验证,并自动为页的“IsValid”赋值;⑦数据绑定发生在各个控件的PreRender事件之后。
下表给出了整个页生命周期的各个阶段,以及发生的顺序:
表2整个页生命周期的各个阶段及发生顺序
阶段
说明
用户可否访问
页/控件事件
页请求
页请求发生在页生命周期开始之前。
用户请求页时,ASP.NET将确定是否需要分析和编译页(从而开始页的生命周期),或者是否可以在不运行页的情况下发送页的缓存版本以进行响应。
不可以
开始
在开始阶段,将设置页属性,如Request和Response。
在此阶段,页还将确定请求是回发请求还是新请求,并设置IsPostBack属性。
此外,在开始阶段期间,还将设置页的UICulture属性。
不可以
页初始化
页初始化期间,可以使用页中的控件,并将设置每个控件的UniqueID属性。
此外,任何主题都将应用于页。
如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。
可以
Page_PreInit
Page_Init
Page_InitComplete
加载
加载期间,如果当前请求是回发请求,则将使用从视图状态和控件状态恢复的信息加载控件属性。
可以
Page_PreLoad
Page_Load
Page_LoadComplete
验证
在验证期间,将调用所有验证程序控件的Validate方法,此方法将设置各个验证程序控件和页的IsValid属性。
不可以
回发事件处理
如果请求是回发请求,则将调用所有事件处理程序。
可以
ControlPostBackEvents
预呈现
做呈现前的最后准备工作,如绑定数据源数据到数据绑定控件上。
可以
Page_PreRender
Page_PreRenderComplete
Page_SaveStateComplete
呈现
在呈现期间,视图状态将被保存到页,然后页将调用每个控件,以将其呈现的输出提供给页的Response属性的OutputStream。
不可以
卸载
完全呈现页、将页发送至客户端并准备丢弃时,将调用卸载。
此时,将卸载页属性(如Response和Request)并执行清理。
可以
Page_Unload
在这里要说明几个问题:
①呈现(Render)是指将页面和控件转换成Html的过程;②在页生命周期的所有阶段中“页请求”、“开始”、“验证”和“呈现”几个阶段,我们是无法通过页或控件的事件来访问和进行控制的。
那么我们又是如何通过事件方法来获取或者改变在各个阶段页和控件的属性值的呢?
微软给出了以下的建议:
表3页事件及典型使用
页事件
典型使用
Page_PreInit
∙使用IsPostBack属性确定是否是第一次处理该页。
∙创建或重新创建动态控件。
∙动态设置主控页。
∙动态设置Theme属性。
∙读取或设置配置文件属性值。
注意
如果请求是回发请求,则控件的值尚未从视图状态还原。
如果在此阶段设置控件属性,则其值可能会在下一阶段被改写。
∙
Page_Init
∙读取或初始化控件属性。
Page_Load
∙读取和更新控件属性。
Controlevents
执行特定于应用程序的处理:
∙如果页包含验证程序控件,请在执行任何处理之前检查页和各个验证控件的IsValid属性。
∙处理特定事件,如Button控件的Click事件。
Page_PreRender
∙对页的内容进行最后更改。
Page_Unload
执行最后的清理工作,可能包括:
∙关闭打开的文件和数据库连接。
∙完成日志记录或其他特定于请求的任务。
注意
在卸载阶段,页及其控件已被呈现,因此无法对响应流做进一步更改。
如果尝试调用方法(如Response.Write方法),则该页将引发异常。
∙
我们在微软建议的基础上给出几个关键点和自己的编程建议:
表4页事件的关键点与编程建议
页事件
关键点
建议
Page_PreInit
(1)IsPostBack已经被赋予正确值。
(2)静态视图状态已经被恢复。
动态设置页主题。
Page_PreLoad
(1)动态视图状态已经被恢复。
根据自己的需要和数据,初始化页面控件。
这包括三种情况:
只在初次请求需要初始化的;只在回发请求需要初始化的;每次请求需要初始化的。
Page_SaveStateComplete
(1)完成了视图状态的保存。
最后一个可以改变控件属性的事件。
我们可以通过为上述那个有一点小错误的例子,设置调试断点,观察所说的关键点。
测试的用例为三个:
①让页面初次加载,我们标记为“初次请求”;
②点击“WUCNumeric1”用户控件的“
”按钮,我们标记为“回发请求
(1)”;
③在“WUCNumeric1”用户控件的“txtNumeric”文本输入框内输入“5”,我们标记为“回发请求
(2)”。
经过调试观察到的结果如下表:
表5调试的结果
页事件
控件事件
初次请求
回发请求
(1)
回发请求
(2)
Page_PreInit
IsPostBack:
false
WUCNumericTuner1.Numeric:
0
IsPostBack:
true
WUCNumericTuner1.Numeric:
0
IsPostBack:
true
WUCNumericTuner1.Numeric:
0
Page_PreLoad
WUCNumericTuner1.Numeric:
0
WUCNumericTuner1.Numeric:
0
WUCNumericTuner1.Numeric:
5
Page_Load
WUCNumericTuner1.Numeric:
0
WUCNumericTuner1.Numeric:
0
WUCNumericTuner1.Numeric:
5
Page_Load
txtNumeric.Text:
0
txtNumeric.Text:
0
txtNumeric.Text:
5
btnUp_Click
txtNumeric.Text:
0
txtNumeric.Text:
1
txtNumeric.Text:
6
Page_LoadComplete
WUCNumericTuner1.Numeric:
0
WUCNumericTuner1.Numeric:
1
WUCNumericTuner1.Numeric:
6
通过调试我们可以看到:
对于“txtNumeric”控件的“Text”属性的静态视图状态“0”在页的“Page_PreInit”事件引发时就已经恢复了,而通过“
”按钮赋值后产生的动态视图状态“1”是在“WUCNumericTuner1”的“btnUp_Click”事件中才得到,而我们在页事件“Page_Load”中编写了代码:
“this.lblSum.Text=(this.WUCNumericTuner1.Numeric+this.WUCNumericTuner2.Numeric).ToString();”,以此得到两者相加的结果,但是页事件“Page_Load”是在“btnUp_Click”事件之前就被引发的,因此我们怎样都得不到正确的相加结果。
同样地,当我们在“WUCNumeric1”的文本框内输入一个数“5”,所产生的是一个交互方式的动态视图状态,而这个状态是在页的“Page_PreLoad”事件引发之前被恢复的,同时在“btnUp_Click”事件中进一步被代码增加了一个“1”,最终变成了“6”,可两者相加的求和代码仍然是在页的“Page_Load”事件中,因此,也是无法得到正确的求和结果的。
这就是开篇的时候看到的错误的原因所在。
通过上述的例子,可以看到,我们了解“页生命周期”的概念,对于正确编写ASP.Net程序来说是十分重要的。
接下来,我们给出编写ASP.Net页面程序和开发Web用户控件的一些建议。
5.改进的例子与编程建议
现在,我们来改进开篇的例子,让它能够正常工作。
为此,我们只需要将代码“this.lblSum.Text=(this.WUCNumericTuner1.Numeric+this.WUCNumericTuner2.Numeric).ToString();”移入页的事件“Page_LoadComplete”中。
代码5求解两数相加的结果
protectedvoidPage_LoadComplete(objectsender,EventArgse)
{
this.lblSum.Text=(this.WUCNumericTuner1.Numeric+
this.WUCNumericTuner2.Numeric).ToString();
}
然后,我们让用户控件“WUCNumericTuner”功能增强一些,为它增加一个属性“Step”表示调整的步长。
开始的例子中,一次调整只能改变一个单位的整数“1”,这是很难满足用户需求的。
为此,我们在“WUCNumericTuner”控件的后台代码中,增加以下代码段:
代码6“WUCNumericTuner”增加的属性“Step”
privateintm_Step=1;
[Browsable(true)]
[Category("可访问性")]
[Description("调整步长")]
publicintStep
{
get
{
returnm_Step;
}
set
{
m_Step=value;
}
}
稍微解释一下,上述代码中定义了一个私有成员变量“m_Step”用于记录调整步长,而属性“Step”不仅封装了“m_Step”私有成员变量,而且其上面的代码是给属性“Step”定义了元数据,用于在控件属性设计器中为其设置静态视图状态,具体见下图:
图6属性设计器中设置属性静态视图状态
同时,我们将“
”和“
”两个按钮的“Click”事件改造为以下代码:
代码7调整按钮的Click