<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
第6章庖丁解牛系列—页面状态机制(视图状态和控件状态)
本章内容
6.1 页面状态概述
6.2 视图状态机制
6.3 控件状态机制
6.4 视图状态和控件状态的关系
6.5 加密页面状态
6.6 清除页面状态
6.7 对动态添加控件的视图状态分析
6.8 自定义类型转换器实现高效率序列化
6.9 页面状态性能优化策略
6.10 视图状态和控件状态的总结
6.1 页面状态概述
在ASP.NET技术的服务器处理机制中,服务器每处理完客户端的一个请求就认为任务结束,当客户端再次请求时,服务器会将其作为一次新的请求处理,即使是相同的客户端也是如此。也就是说服务器不会保存两次请求之间的一些前后相接的数据,这对开发人员经常实现一个前后衔接的操作来说就比较麻烦了,比如输入一些信息到一个文本中,然后提交一个按钮,很多时候我们要在按钮提交的服务端事件中处理提交之前的数据和提交按钮时用户输入的最新数据,即想同时得到文本框的旧值和新值,但是服务端不会保存前一个请求的任何信息,那怎么才能做到这一点呢?
两次页面请求之间的数据关联性,ASP.NET是通过视图机制实现的,简单地讲,视图区域信息(ViewState)存储于页面上的一个隐藏字段(名为__VIEWSTATE,只是视图状态中的值经过哈希计算和压缩,并且针对Unicode实现进行编码,其安全性要高于我们自己设置的隐藏域控件),每次需要视图机制保存的一些信息都存储在此字段中,每次提交时,它都会以“客户端到ó服务端”的形式来回传递一次,当处理完成后,最后会以处理后的新结果作为新的ViewState存储到页面中的隐藏字段,并与页面内容一起返回到客户端。
视图机制支持很多类型的数据存储,其中基本类型的有字符串、数字、布尔值、颜色、日期、字节,各种类型的数组等。视图机制已经对一些如ArrayList和哈希表集合等类型对象进行了优化;除了基本类型视图状态视图机制还支持自定义的类型,由于ViewState数据是作为序列化格式串存储的,因此默认情况下使用.NET Framework提供的二进制序列化功能来序列化对象,对于一些比较复杂的对象,一般都使用专门的类型转换器TypeConvert序列化,要比默认.NET提供的二进制序列化节省资源。关于TypeConvert类的实现在第4章已经讲了很多例子了,在后面会介绍类型转换器应用于视图状态的说明和示例。
为了提高性能,通常禁用页面或禁用服务端控件的状态视图,有些控件不需要维护其状态,如Label控件只是显示文本,而标签的文本,值不参与回发,可以设置其属性:EnableViewState=false;
如果整个页面控件都不需要维持状态视图,则可以设置整个页面的状态视图为false:<%@ Page EnableViewState="false"%>。
由于控件内部使用的视图状态,这样会导致视图状态失效,甚至会产生致命的问题-控件无法使用。说明一点,禁用视图是合法的,一个好的控件应该允许视图状态在适当情况下被开发人员禁用,并且仍然能够正确运行。
为了解决这个问题,ASP.NET 2.0开始支持控件状态机制。控件的状态数据现在能通过控件状态而不是视图状态被保持,控件状态是不能够被禁用的。如果控件中需要保存控件之间的逻辑,比如选项卡控件要记住每次回发时当前已经选中的索引SelectIndex时,就适合使用控件状态。当然ViewState属性完全可以满足此需求,如果视图状态被禁用的话,自定义控件就不能正确运行。控件状态的工作方式与视图状态完全一致,并且默认情况下在页面中它们都是存储在同一个隐藏域中。
总结一下,一般开发人员主要通过以下三种方式使用ASP.NET视图:
1.使用基类提供的ViewState对象
直接访问基类Control中的ViewState对象,类型为StateBag,以键/值对的形式存储数据
2.自定义类型视图状态。
重写控件的默认方法(SaveViewState,LoadViewState),实现自定义类型的视图状态。一般需要与属性对应类类型的视图状态配合使用,类类型视图状态可能通过实现IStateManager接口的几个成员(方法和属性)实现。
3.控件状态
它也提供了可重写的方法(SaveControlState,LoadControlState),实现控件中属性的控件状态。
视图状态数据在每次请求过程中都要在客户端和服务端来回传递,因此在开发过程中要确保数据量不要太大,否则会出现网络传输瓶颈。
从下节开始,详细讲解页面状态(视图状态和控件状态)的内部机制,以及它们在自定义控件中的应用。
6.2 视图状态机制
6.2.1 IStateManager接口
.NET框架为自定义视图状态管理提供了System.Web.UI.IStateManager接口,定义了任何类为支持服务器控件的视图状态管理而必须实现的属性和方法,服务器控件的视图状态由控件属性的累计值组成。该接口包括保存并加载服务器控件的视图状态值的方法,以及一个指示控件跟踪其视图状态的更改的方法。此接口的成员与Control类中的对应方法具有相同的语义。
若要自定义ASP.NET应用程序管理服务器控件视图状态的方式,必须创建一个实现此接口的类。代码如下:
- ///<summary>
- ///获得本书更多内容,请看:
- ///http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- ///</summary>
- publicinterfaceIStateManager
- {
- //Methods
- objectSaveViewState();
- voidLoadViewState(objectstate);
- voidTrackViewState();
- //Properties
- boolIsTrackingViewState{get;}
- }
该接口包括以下几个成员:
Ø SaveViewState:保存自从页回发到服务器后发生的所有服务器控件视图状态更改,最后返回最新更改后的视图状态对象。如果没有与控件关联的视图状态,则此方法返回空。保存了视图状态后,页面类会把所有控件的视图状态对象转换为可以通过网络传输的Base64格式字符串形式,最终该字符串对象作为存储在Hidden元素中的变量返回给客户端。使用自定义视图状态时,一般使用SaveViewState和LoadViewState组合完成状态管理。
Ø LoadViewState:把SaveViewState方法保存的上一个页面的视图信息还原到控件复杂属性中。
Ø TrackViewState:在服务器控件的生存期内,将在Init事件结束时自动调用该方法。在开发模板数据绑定控件时调用此方法。此方法提醒ASP.NET监视服务器控件视图状态的更改。如果控件没调用TrackViewState()方法,则本次对控件属性的修改将不会被添加到__VIEWSTATE隐藏域中,下次页面回发时,控件的属性只恢复为之前的旧值。从性能角度讲,为了减少在网络上的传输量,应该只保存“变化”的数据到视图状态中,即仅对需要保存到视图中的数据才调用此方法。其实TrackViewState只是控制一个布尔值作标记,往视图中增加数据时,会判断该值是否为true,如果为true才将其加入视图数据。下节讲解StateBag类时还会说明其内部原理。
Ø IsTrackingViewState:返回当前控件视图是否被ASP.NET框架监视(是否存储该属性到视图中,与TrackViewState方法控制的是同一个标记)。
或许读者会想到,之前在开发控件时使用过视图存储属性值,如ViewState["Text"],而没有使用IStateManager接口控件为什么这样也能够正确保存值呢?在后面的6.2.3小节会说明其原因,事实上它也是使用了IStateManger接口,只是Control提供了更方便的管理而已。
6.2.2 控件生命周期中的装载和保存视图阶段
在第1章中讲过控件周期阶段,其中就包括视图状态的阶段,如图6-1所示。
从图6-1中可以看到LoadViewState和SaveViewState分别在控件生命周期的开始(初始化Init后)和最后(呈现Render之前)。这样我们可以在其间的一些周期阶段操作视图状态数据。而在控件的基类Control中已经提供了对这两个方法的支持。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f" o:preferrelative="t"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path gradientshapeok="t" o:connecttype="rect" o:extrusionok="f"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 99pt; HEIGHT: 241.5pt" type="#_x0000_t75" o:ole=""><imagedata src="file:///D:%5CDOCUME~1%5CZHENGJ~1%5CLOCALS~1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image001.emz" o:title=""></imagedata></shape>
图6-1 控件生命周期中的视图装载和保存阶段
对于自定义的类型,仅实现IStateManager接口的方法是不够的(该方法仅使自定义类具有正反序列化的能力),还需要由主控件的控件生命周期方法来引发调用它们,才能够正确地装载和保存视图数据。这就要求主控件直接或间接继承Control类,并重载Control类中的LoadViewState和SaveViewState方法,这两个方法属于控件生命周期阶段方法,只要是属于控件生命周期的方法,则在控件生成阶段一定会被页框架调用。它们才是视图状态启动的导火线。
重载这两个方法如下所示:
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
public class ViewStatePeriod : WebControl
{
protected override object SaveViewState()
{
//… …
}
protected override void LoadViewState(object savedState)
{
//… …
}
}
u 另外,视图状态监视是在初始化Init阶段完成后启动的,之后就可以监控对视图的操作了。少数情况下,如果在视图状态打开之前想操作视图对象,则要手动启用跟踪:
if (this.IsTrackingViewState == false)
{
this.TrackViewState();
... ...//操作视图
}
6.2.3 简单类型视图状态应用
视图状态默认支持很多类型的数据存储,其中基本类型的有字符串、数字、布尔值、颜色、日期、字节,以及各种类型的数组等。以下是一个最常见的典型用法:
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["Text"] = value;
}
}
u 在上面代码中有个ViewState的对象,此对象没有多么深奥,只是基类Control中定义的一个属性。追溯到它的基类定义,代码如下:
private StateBag _viewState;
[WebSysDescription("Control_State"), Browsable(false), Designer Serializa
tionVisibility(DesignerSerializationVisibility.Hidden)]
protected virtual StateBag ViewState
{
get
{
if (this._viewState == null)
{
this._viewState = new StateBag(this.ViewStateIgnoresCase);
if (this.IsTrackingViewState)
{
this._viewState.TrackViewState();
}
}
return this._viewState;
}
}
u 这是一个标准的自定义类型属性。再仔细看一下,该属性的类型为StateBage类,这才是我们要找的关键类,它的代码结构如下:
- ///<summary>
- ///获得本书更多内容,请看:
- ///http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- ///</summary>
- publicsealedclassStateBag:IStateManager,IDictionary,ICollection,IEnumerable
- {
- //Fields
- privateIDictionarybag;
- privateboolignoreCase;
- privateboolmarked;
- //Methods
- publicStateBag()
- :this(false)
- {
- }
- publicStateBag(boolignoreCase)
- {
- this.marked=false;
- this.ignoreCase=ignoreCase;
- this.bag=this.CreateBag();
- }
- publicStateItemAdd(stringkey,objectvalue)
- {
- if(string.IsNullOrEmpty(key))
- {
- throwExceptionUtil.ParameterNullOrEmpty("key");
- }
- StateItemitem=this.bag[key]asStateItem;
- if(item==null)
- {
- if((value!=null)||this.marked)
- {
- item=newStateItem(value);
- this.bag.Add(key,item);
- }
- }
- elseif((value==null)&&!this.marked)
- {
- this.bag.Remove(key);
- }
- else
- {
- item.Value=value;
- }
- if((item!=null)&&this.marked)
- {
- item.IsDirty=true;
- }
- returnitem;
- }
- publicvoidClear()
- {
- this.bag.Clear();
- }
- privateIDictionaryCreateBag()
- {
- returnnewHybridDictionary(this.ignoreCase);
- }
- publicIDictionaryEnumeratorGetEnumerator()
- {
- returnthis.bag.GetEnumerator();
- }
- publicboolIsItemDirty(stringkey)
- {
- StateItemitem=this.bag[key]asStateItem;
- return((item!=null)&&item.IsDirty);
- }
- internalvoidLoadViewState(objectstate)
- {
- if(state!=null)
- {
- ArrayListlist=(ArrayList)state;
- for(inti=0;i<list.Count;i+=2)
- {
- stringkey=((IndexedString)list[i]).Value;
- objectobj2=list[i+1];
- this.Add(key,obj2);
- }
- }
- }
- publicvoidRemove(stringkey)
- {
- this.bag.Remove(key);
- }
- internalobjectSaveViewState()
- {
- ArrayListlist=null;
- if(this.bag.Count!=0)
- {
- IDictionaryEnumeratorenumerator=this.bag.GetEnumerator();
- while(enumerator.MoveNext())
- {
- StateItemitem=(StateItem)enumerator.Value;
- if(item.IsDirty)
- {
- if(list==null)
- {
- list=newArrayList();
- }
- list.Add(newIndexedString((string)enumerator.Key));
- list.Add(item.Value);
- }
- }
- }
- returnlist;
- }
- publicvoidSetDirty(booldirty)
- {
- if(this.bag.Count!=0)
- {
- foreach(StateItemiteminthis.bag.Values)
- {
- item.IsDirty=dirty;
- }
- }
- }
- publicvoidSetItemDirty(stringkey,booldirty)
- {
- StateItemitem=this.bag[key]asStateItem;
- if(item!=null)
- {
- item.IsDirty=dirty;
- }
- }
- voidICollection.CopyTo(Arrayarray,intindex)
- {
- this.Values.CopyTo(array,index);
- }
- voidIDictionary.Add(objectkey,objectvalue)
- {
- this.Add((string)key,value);
- }
- boolIDictionary.Contains(objectkey)
- {
- returnthis.bag.Contains((string)key);
- }
- voidIDictionary.Remove(objectkey)
- {
- this.Remove((string)key);
- }
- IEnumeratorIEnumerable.GetEnumerator()
- {
- returnthis.GetEnumerator();
- }
- voidIStateManager.LoadViewState(objectstate)
- {
- this.LoadViewState(state);
- }
- objectIStateManager.SaveViewState()
- {
- returnthis.SaveViewState();
- }