18.3 视图状态与控件状态
关于视图状态与控件状态,上一章也做了比较详细的阐述。其实,视图状态与控件状态类似,但控件状态在功能上独立于视图状态。网页开发人员可能会出于性能等原因而禁用整个页面或单个控件的视图状态,但他们却不能禁用控件状态。
控件状态是专为存储控件的重要数据(如一个页面控件的页数)而设计的,回发时必须用到这些数据才能使控件正常工作(即便禁用视图状态也不受影响)。默认情况下,ASP.NET页框架将控件状态存储在页的一个隐藏元素中,视图状态也同样存储在此隐藏元素中。即使禁用视图状态,或使用Session管理状态时,页面中的控件状态仍会传输至客户端,然后返回到服务器。在回发时,ASP.NET会对隐藏元素的内容进行反序列化,并将控件状态加载到每个注册过控件状态的控件中。
需要说明的是,只能够对那些在回发过程中对控件至关重要的少量关键数据使用控件状态,而不要将控件状态作为视图状态的备用选项使用。
在下面的示例中,创建一个名为IndexButton的自定义控件,该控件派生自Button类,如代码清单18-4所示。
代码清单18-4 IndexButton.cs
using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace_18_1
{
[AspNetHostingPermission(SecurityAction.Demand,
Level=AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level=AspNetHostingPermissionLevel.Minimal),
ToolboxData("<{0}:IndexButton runat=\"server\">
</{0}:IndexButton>")]
public class IndexButton:Button
{
private int indexValue;
为了演示视图状态与控件状态,下面需要在控件里分别定义两个属性。
首先定义了一个Index属性,并将该属性保存在控件状态中;再定义了一个IndexInViewState属性,该属性存储在ViewState字典中。如下面的代码所示:
[Bindable(true),
Category("Behavior"),
DefaultValue(0),
Description("The index stored in control state.")]
public int Index
{
get
{
return indexValue;
}
set
{
indexValue=value;
}
}
[Bindable(true),
Category("Behavior"),
DefaultValue(0),
Description("The index stored in view state.")]
public int IndexInViewState
{
get
{
object obj=ViewState["IndexInViewState"];
return(obj==null)?0:((it)obj;
}
set
{
ViewState["IndexInViewState"]=value;
}
}
定义好Index属性与IndexInViewState属性之后,还必须重写如下三个方法才能使控件参与控件状态:1)重写OnInit方法并调用RegisterRequiresControlState方法向页面注册,以参与控件状态。必须针对每个请求完成此任务。
2)重写SaveControlState方法,以在控件状态中保存数据。
3)重写LoadControlState方法,以从控件状态加载数据。此方法调用基类方法,并获取基类对控件状态的基值。如果indexValue字段不为零,而且基类的控件状态也不为空,Pair类便可作为方便的数据结构使用,用来保存和还原由两部分组成的控件状态。
如下面的代码所示:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override object SaveControlState()
{
object obj=base.SaveControlState();
if(indexValue!=0)
{
if(obj!=null)
{
return new Pair(obj, indexValue);
}
else
{
return(indexValue);
}
}
else
{
return obj;
}
}
protected override void LoadControlState(object state)
{
if(state!=null)
{
Pair p=state as Pair;
if(p!=null)
{
base.LoadControlState(p.First);
indexValue=((it)p.Second;
}
else
{
if(state is int)
{
indexValue=((it)state;
}
else
{
base.LoadControlState(state);
}
}
}
}
}
}
这样,一个带视图状态与控件状态的自定义服务器控件IndexButton就完成了。
为了演示IndexButton控件的使用,需要创建一个测试页面WebForm1.aspx。该页首先通过在@Page指令中将EnableViewState特性设置为false来使页面禁用视图状态。如下面的代码所示:
<%@Page Language="C#"AutoEventWireup="true"
EnableViewState="false"CodeBehind="WebForm1.aspx.cs"
Inherits="_18_1.WebForm1"%>
<%@Register Assembly="18-1"Namespace="_18_1"TagPrefix="cc1"%>
<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1"runat="server">
<cc1:IndexButton Text="IndexButton"
ID="IndexButton1"runat="server"/>
<br/><br/>
"Index"值:<br/>
<asp:Label ID="Label1"runat="server">
</asp:Label>
<br/><br/>
"IndexInViewState"值:
<br/>
<asp:Label ID="Label2"runat="server">
</asp:Label>
<br/>
</form>
</body>
</html>
接下来,还需要在页面的Page_Load事件处理程序中,将IndexButton1控件的Index和IndexInViewState属性的值分别加1,然后将其分别显示在页的Label1与Label2标签中。如下面的代码所示:
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text=((IdexButton1.Index++).ToString();
Label2.Text=((IdexButton1.IndexInViewState++).ToString();
}
由于Index属性存储在无法禁用的控件状态中,因而Index属性会在回发时维护其值,并在每次将页回发到服务器时加1。相比之下,因为IndexInViewState属性存储在视图状态中,而页的视图状态已被禁用,所以IndexInViewState属性始终为默认值零。因此,其运行结果如图18-5所示。
当然,如果启用视图状态,那么IndexInViewState属性的结果将与Index属性一样。如下面的代码所示:
<%@Page Language="C#"AutoEventWireup="true"
EnableViewState="true"CodeBehind="WebForm1.aspx.cs"
Inherits="_18_1.WebForm1"%>
其运行结果如图18-6所示。
图 18-5 EnableViewState="false"的运行结果
图 18-6 EnableViewState="true"的运行结果
最后,还需要说明的是,与控件状态一样,如果需要更大的灵活性来定制怎么存储视图状态,则可以通过覆盖TrackViewState、SaveViewState和LoadViewState方法来实现。其中,SaveViewState方法总是在控件被呈现成HTML之前被调用。可以从这个方法返回一个单一的可序列化的对象,这个对象将被保存在视图状态里。类似地,当控件在后续的回传中被重新创建时,LoadViewState方法将被调用。接受作为一个参数保存的对象,然后就可以用它来配置控件的属性了。