
示例代码
using ...System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page 
...{
protected void Page_Init(object sender, EventArgs e) 
...{
ddlTest.Items.Add(new ListItem("Init", "Init"));
}
protected void Page_Load(object sender, EventArgs e) 
...{
ddlTest.Items.Add(new ListItem("Load", "Load"));
}
}最近看见有朋友说Page_Init只执行一次,我始终将信将疑,于是自己测试一下,结果发现在每次单击按钮的时候还真的不会再增加Init这样的列表项了。难道Page_Init还真的是只执行一次么?空想也不是解决办法,打个断点跟踪下吧,发现每次回发时Init是会执行的,由此可见出现这种情况是和Init无关的(她是无辜的)。真正的幕后'黑手'又会是谁呢?
一、页面生命周期
一个ASP.NET页面大致可以分为一下几个阶段:
1,因为它也是一个类,所以构造函数是必定首当其冲的,不过我们一般很少会用到它。
2,初始化阶段。在这个阶段,asp.net为我们细化了三个方法:Page_PreInit,Page_Init,Page_InitComplete,他们的方法签名和Page_Load完全一样,全是 void Page_XXX( object sender, EventArgs e )..的形式。而方法名称也都是固定的(这些可以通过查看TemplateControl源码看到)。
3,Load阶段。这个阶段我们可以通过这几个方法来对页面进行控制:Page_PreLoad,Page_LoadComplete,Page_Load。
4,如果有定义了引起回发的控件的相应事件处理程序(比如按钮,点击后引起回发,这时如果你在后台定义了该按钮的Click事件处理程序),则执行该事件处理程序(会在Page_LoadComplete执行前执行)。
5,呈现。同样,有这样几个方法:Page_PreRender,Page_PreRenderComplete,Render。
6,卸载页面,释放资源:Page_Unload
注:这些方法和相应的事件绑定是通过在aspx页面的@Page指令里设置AutoEventWireup="true"来完成的。如果去掉这个属性,则这些方法将不再发挥作用。
下面看个小例子:

点击展开示例
using ...System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class Default2 : System.Web.UI.Page 
...{
protected void Page_Load(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_Load<br/>"));
}
protected void Page_PreInit(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreInit<br/>"));
}
protected void Page_Init(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_Init<br/>"));
}
protected void Page_InitComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_InitCompleted<br/>"));
}
protected void Page_PreLoad(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreLoad<br/>"));
}
protected void Page_LoadComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_LoadCompleted<br/>"));
}
protected void Page_PreRender(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreRender<br/>"));
}
protected void Page_PreRenderComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreRenderComplete<br/>"));
}
protected override void Render(HtmlTextWriter writer) 
...{
Controls.Add(ParseControl("Render<br/>"));
base.Render(writer);
}
protected void Page_Unload(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_Unload<br/>"));
}
}执行的结果如下:

(注:相对于Controls.Add的方式我更倾向于使用Form.Controls.Add。因为查看生成的页面的源码发现新添加的控件跑到了html结束标签的后面。)
二、寻找'黑手'
上面简单的介绍了页面的生命周期,下面就让我们来寻找造成前面问题的'凶手'吧。其实从文章题目大家也基本上可以猜测到我想说'凶手'其实就是ViewState。
说到ViewState就必须要提及与它息息相关的三个方法:LoadViewState,SaveViewState,TrackViewState。
先看下这三个方法会出现在页面生命周期的哪个部分呢,还是看代码吧:

点击展开.aspx示例

点击展开.cs示例
using ...System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class Default2 : System.Web.UI.Page 
...{
public string Text 
...{
get 
...{
return ViewState["Text"] == null ? string.Empty : ViewState["Text"].ToString();
}
set 
...{
ViewState["Text"] = value;
}
}
protected void Page_Load(object sender, EventArgs e) 
...{
if (!IsPostBack) 
...{
Text = "test";
}
Controls.Add(ParseControl("Page_Load<br/>"));
}
protected void Page_PreInit(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreInit<br/>"));
}
protected void Page_Init(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_Init<br/>"));
}
protected void Page_InitComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_InitCompleted<br/>"));
}
protected void Page_PreLoad(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreLoad<br/>"));
}
protected void Page_LoadComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_LoadCompleted<br/>"));
}
protected void Page_PreRender(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreRender<br/>"));
}
protected void Page_PreRenderComplete(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_PreRenderComplete<br/>"));
}
protected override void Render(HtmlTextWriter writer) 
...{
Controls.Add(ParseControl("Render<br/>"));
base.Render(writer);
}
protected void Page_Unload(object sender, EventArgs e) 
...{
Controls.Add(ParseControl("Page_Unload<br/>"));
}
protected override void LoadViewState(object savedState) 
...{
Controls.Add(ParseControl("LoadViewState<br/>"));
base.LoadViewState(savedState);
}
protected override void TrackViewState() 
...{
Controls.Add(ParseControl("TrackViewState<br/>"));
base.TrackViewState();
}
protected override object SaveViewState() 
...{
Controls.Add(ParseControl("SaveViewState<br/>"));
return base.SaveViewState();
}
}结果如下:
1.在页面第一次加载时:

2.点击按钮产生回发时:

可以发现在回发时多了一个LoadViewState,它做了什么事情呢?它的工作就是从页面的_ViewState隐藏域中恢复视图状态。那在这个阶段之前对视图状态的更改呢?结果是将会被覆盖掉。于是对应于我们的DropDownList的例子,当第一次执行时为其动态添加的两个item都将被放到视图状态中(通过SaveViewState),而在产生回发时,尽管在执行到Init时又添加了Item,到了LoadViewState的时候,DropDownList的Items又会恢复成上一次保存到视图状态时的情形(同时接下来在调用SaveViewState方法时,又会将当前的Items放到视图状态中去)。如此反复,也就难怪只能看见一直增加Load这样的Item了。
三、为ViewState正名
郑重声明,ViewState是个好人。