2.2.4 StateBag类
ViewState是控件的一个属性,用来使用控件具有记忆功能。在前边的讲述中,我们可以看到控件的一些属性通过使用ViewState能够恢复原来的值,保存本次的值,在Control类中很多方法的实现也是直接调用了ViewState的方法。ViewState的类型是StateBag,下面我们就了解一下在StateBag中是如何实现这些功能的。StateBag定义在System.Web.UI中声明如下:
public sealed class StateBag : IStateManager, IDictionary, ICollection, IEnumerable
StateBag类可以理解为是一个具有状态管理功能的字典,因为它实现了IStateManager, IDictionary 这两个接口。StateBag类可以象字典那样保存Key/Value对,其中Key是字符串而Value是对象。下面是一个使用StateBag的例子。
protected void Button2_Click(object sender, EventArgs e)
{
StateBag TestSB = new StateBag();
TestSB["b"] = "bbbbb";
TextBox1.Text = TestSB["b"].ToString();
}
在上面的例子中使用StateBag保存一个Key为“b”,其值为“bbbbb”的Key/Value对。ViewState属性也是StateBag的一个实例,当然也就可以象上面那样使用。在ViewState中保存了很多的Key/Value对(键值对),这些Key/Value对用来保存控件的属性,这些Key/Value对是有ASP.Net来维护的。当然我们也可以增加一些自己的Key/Value对,来保存一些信息。
StateBag还实现System.Web.UI.IStateManager接口,这样它具有状态管理功能。下面对StateBag如何提供状态管理功能进行说明。
1) StateItem类
StateBag中保存Key/Value对,Key是String类型,Value是Object类型。但是在StateBag内部保存Value不是Object类型,而是将Object类型转换为StateItem类型然后保存,从StateBag中取出的时候再将StateItem类型转换为Object类型,也就是说StateBag中的Key/Value对实际上是String/StateItem类型。转换过程是在StateBag内部实现客户感觉不到。StateItem的声明如下:
public sealed class StateItem
{
internal StateItem(object initialValue);
public bool IsDirty { get; set; }
public object Value { get; set; }
}
通过上面的代码我们可以看出实际上多了一个IsDirty属性,来标记当前的Value是否已经被修改过。
2) Add
Add方法是将传入的Key,Value保存到字典中,并处理IsDirty属性。在StateBag.LoadViewState方法中会调用Add方法。其示意代码如下:
public StateItem Add(string key, object value)
{
StateItem item = this.bag[key] as StateItem;
if (item == null)
{
if ((value != null) || this.marked)
{
item = new StateItem(value);
this.bag.Add(key, item);
}
}
else
{
item.Value = value;
}
if ((item != null) && this.marked)
{
item.IsDirty = true;
}
return item;
}
虽然函数的名称是Add,其实也包括了更新。如果当前的项在字典中不存在则新增,否则更新。新增时新建一个StateItem类型的对象item,将Key和item增加到字典中。如果Item不为null,并且跟踪状态标记为true,则item的IsDirty为true。什么情况下会调用Add方法呢?主要有两种情形一种是StateBag.LoadViewState,在下面会具体介绍到。还有一种情况就是对控件的属性赋值的时候,比如Button.Text=”button”,此时会调用Text属性的Set,在Set中执行的代码this.ViewState["Text"] = value,这个代码实际上执行this.ViewState.Add("Text",value)。
3) LoadViewState
还原以前保存的ViewState。将传入的ArrayList对象加载到字典中。示意代码如下:
internal void LoadViewState(object state)
{
ArrayList list = (ArrayList) state;
for (int i = 0; i < list.Count; i += 2)
{
string key = ((IndexedString) list[i]).Value;
object obj2 = list[i + 1];
this.Add(key, obj2);
}
}
我们知道在初始化阶段StateBag.TrackViewState都已经被调用过了,也就是说marked为true了,这样在StateBag.LoadViewState中调用Add方法第一次加载完ViewState的数据后,所有的StateItem的IsDirty属性都是true。
4) SaveViewState
将字典中已经修改过的Key/Value存放在一个ArrayList对象中返回。
internal object SaveViewState()
{
ArrayList list = null;
IDictionaryEnumerator enumerator = this.bag.GetEnumerator();
while (enumerator.MoveNext())
{
StateItem item = (StateItem) enumerator.Value;
if (item.IsDirty)
{
list.Add(new IndexedString((string) enumerator.Key));
list.Add(item.Value);
}
}
}
return list;
}
在SaveViewState中只会将字典中item.IsDirty=true的项目返回,哪些项的IsDirty=true呢?通过上面对Add方法及LoadViewState的分析我们知道,当我们第一次设置了控件的某个属性后会调用Add方法,这个属性对应的StateItem的属性IsDirty会被设置为true,在SaveViewState时会保存这个item。在下次请求时在LoadViewState中也会将IsDirty设置为true,在SaveViewState是会保存这个item。总之只要控件属性的被修改过和默认值不一致都会一直被保存,即使这个属性的值仅仅被修改过一次,之后保存不不变,也会在多次PostBack之间保存起来。
5) TrackViewState
在TrackViewState方法中,设置跟踪标记为True,其目的就是开始状态跟踪了。示意代码如下:
internal void TrackViewState()
{
this.marked = true;
}
3 ViewState与ControlState
ControlState是一个自定义的状态保持机制,也就是说保持状态的机制需要开发人员自己去完成,而不像ViewState,它有自己默认的状态保持机制。既然已经有了ViewState为什么还需要ControlState呢?因为ViewState是可以被禁用的,而ControlState却不能被禁用,对于有些必须保存的信息,就可以使用ControlState。ControlState的实现思路基本上与ViewState类似,ControlState需要保存的信息也被序列化后保存在__VIEWSTATE中。使用ControlState,需要在OnInit 方法中调用 RegisterRequiresControlState向页面注册,而且要重写SaveAdapterControlState,LoadAdapterControlState这两个方法自己,实现要保存什么,怎样保存。
4 ViewState的使用
4.1 ViewState的优缺点
使用ViewState首先要了解ViewState与其他的保持状态机制相比有什么优缺点。
使用ViewState具有以下优点:
一、耗费的服务器资源较少(与Application、Session相比)。因为,视图状态数据都写入了客户端计算机中。
二、易于维护。默认情况下,DotNet系统自动启用对状态数据的维护。
三、因为它不使用服务器资源、不会超时,并且适用于任何浏览器。
使用视图状态具有以下缺点:
一、性能问题。由于视图状态存储在页本身,因此如果存储较大的值,用户显示页和发送页时的速度仍然可能减慢。ViewState 增加了发送到浏览器的页面的大小,同时也增加了回传的窗体的大小,因此不适合存储大量数据
二、设备限制。移动设备可能没有足够的内存容量来存储大量的视图状态数据。
三、潜在的安全风险。视图状态存储在页上的一个或多个隐藏域中。虽然视图状态以哈希格式存储数据,但它可以被篡改。如果直接查看页输出源,可以看到隐藏域中的信息,尽管 ViewState 数据已被编码,并且可以选择对其进行加密,但始终不将数据发送到客户端才是最安全的。
4.2 ViewState安全性
ViewState将一些信息保存在客户端,而且默认情况下客户端的数据仅仅是进行了Base64编码了,很容易被看到原文。当然ViewState还有一些安全性措施来改善这一点。ViewState 安全性对于处理和呈现 ASP.NET 页面所需的时间有直接的影响。简单地说,安全性越高,速度越慢。因此如果不需要,不要为 ViewState 添加安全性。
4.2.1 MAC
MAC是message authentication check的简写即消息验证检查。MAC可以防止ViewState数据被篡改。可以通过设置EnableViewStateMAC="true"属性来启用消息验证检查。可以在页面级别上设置 EnableViewStateMAC,也可以在应用程序级别上设置。有一点需要特别说明的是在MSDN中说EnableViewStateMAC的默认值是Flase,但是实际上发现EnableViewStateMAC的默认值是True,这一点大家可以Page_Load中输出EnableViewStateMAC属性来验证。也就是说默认情况下都是对ViewState进行防篡改处理的。下面的描述结合“2.2.1Page中的处理”中的“4)ViewState序列化与反序列化”进行理解。
在ObjectStateFormatter中进行序列化的时候如果EnableViewStateMAC="true",则在MachineKeySection.GetEncodedData中根据字节流buf计算出一个20位的字节表示Hash值,增加在buf字节流的后边,形成一个新的字节流。
在ObjectStateFormatter中进行反序列化的时候如果EnableViewStateMAC="true",则在MachineKeySection.GetDecodedData中取buf的前(length-20)位计算出一个20位的字节表示Hash值,将这个Hash值与buf的最后20位进行比较,如果不一直则说明ViewState被篡改了则抛出异常。
默认情况下,.NET框架使用SHA1算法来生成ViewState散列代码。此外,也可以通过在machine.config文件中设置<machineKey>来选择 MD5 算法,如下所示:<machineKey validation="MD5" />。MD5算法的性能要比SHA1算法好一些,但是同样不够安全。
4.2.2加密
默认情况下__VIEWSTATE中存储的值仅仅仅进行了Base64编码,并没有进行加密,如果ViewState中有一些敏感信息需要增加安全性,我们也可以对ViewState进行加密。我们可以设置ViewStateEncryptionMode的值来决定是否加密,其值是“Auto”,“Always”,“Never”,默认值是“Auto”。“Always”表示进行加密,“Never”表示不进行加密,“Auto”时如果调用了RegisterRequiresViewStateEncryption方法后则进行加密。ViewStateEncryptionMode属性不能在代码中设置page指令或者配置文件中使用。
4.2.3设置ViewStateUserKey
设置 ViewStateUserKey 属性有助于防止您的应用程序受到恶意用户的点击式攻击。必须在页处理的 Page_Init 阶段设置此属性。具体的信息可以MSDN中的说明,链接如下:
http://msdn2.microsoft.com/zh-cn/library/ms972969.aspx
4.3 ViewState的禁用
因为ViewState会一定程度上影响性能所以在不需要的时候禁用 ViewState。默认情况下 ViewState 将被启用,并且是由每个控件(而非页面开发人员)来决定存储在 ViewState 中的内容。有时,这一信息对应用程序并没有什么用处。尽管也没什么害处,但却会明显增加发送到浏览器的页面的大小。因此如果不需要使用 ViewState,最好还是将它关闭,特别是当 ViewState 很大的时候。通过将对象的EnableViewState属性设置为False禁用ViewState。可以针对单个控件、整个页面或整个应用程序禁用ViewState,如下所示:
每个控件(在标记上)<asp:datagrid EnableViewState="false" ?/>
每个页面(在指令中) <%@ Page EnableViewState="False" ?%>
每个应用程序(在 web.config 中) <Pages EnableViewState="false" ?/>
以下情况将不再需要ViewState:(1)控件未定义服务器端事件(这时的控件事件均为客户端事件且不参加回送的);(2)控件没有动态的或数据绑定的属性值。
4.4 ViewState优化
ViewState优缺点并存,有些人支持使用,有些人反对使用,也有人提出了对ViewState的优化,主要是压缩ViewState减少传输量及修改ViewState的存储位置。具体可以参考一下的链接。
压缩:http://www.cnblogs.com/mack/archive/2005/07/27/201411.html
修改存储位置:http://czhenq.cnblogs.com/archive/2006/04/03/365807.html