为Web页面及其控件保持状态信息是非常有必要的。然而,由于Web应用程序创建于HTTP协议的顶层,这是一个无状态的协议,因此,保持状态信息则变得非常困难。为了解决这个问题,ASP.NET 2.0技术提供了多种解决方案,例如,利用Session、Cookie、视图状态、控件状态、隐藏域、查询字符串、个性化用户配置(Profile)等等。对于利用ASP.NET 2.0技术创建服务器控件而言,保持状态信息也是非常重要的,其主要解决途径是利用视图状态和控件状态。本文详细讲解了视图状态(ViewState)的基本知识,并通过典型应用介绍视图状态的应用方法。
视图状态概述
视图状态是一项非常重要的技术,它能使得页面和页面中的控件在从服务器到客户端,再从客户端返回的往返过程中保持状态信息。这样就可以在Web这种无状态的环境之上创建一个有状态并持续执行的页面效果。本节主要介绍有关视图状态的运行机制、应用方法、存储的数据类型、性能和安全性、视图状态分块(这是ASP.NET 2.0的新特性)和优缺点等内容。
(1)运行机制
视图状态的具体运行过程为:每当用户请求某个.aspx页面时,.NET框架首先把相关控件的状态数据序列化成一个字符串,然后,将其做为名为__VIEWSTATE的隐藏域的Value值发送到客户端。如果页面是第一次被请求,那么服务器控件也将是被第一次执行时,名为__VIEWSTATE的隐藏域中只包含控件的默认信息,通常为空或者null。在随后的回送事件中,ViewState中就保存了服务器控件在前面回送中可用的属性状态。这样服务器控件就可以监视在当前被处理的回送事件发生之前的状态了。这些过程是由.NET框架负责的,对用户来说是执行.aspx页面就有了持续执行的效果。
(2)存储的数据类型
视图状态可以存储多种类型的数据,并且为了提高运行效率,视图状态自身还包括一套已经优化的针对常用类型的序列化方式。视图状态序列化方式默认支持的数据类型包括以下几种:String、Int32、Unit、Color、Array、ArrayList、HashTable和自定义类型转换器TypeConverter。
视图状态已经为Array、ArrayList和包含上面列出类型的HashTable对象进行了优化。因此,当在控件中使用视图状态时,应该试着限定于使用以上简单数据类型,以及经过优化的类型。在此,需要重点说明一下自定义类型转换器TypeConverter,它提供了一种将值的类型转换为其他类型以及访问标准值和子属性的统一方法。例如,可以利用TypeConverter将字符串转换为数值,或者将数值转换为字符串。如果没有类型转换器,那么页面框架会使用.NET框架提供的二进制序列化功能来序列化对象,这个过程是非常耗费资源的。
(3)性能和安全性
使用视图状态时,对象必须先序列化,然后再通过回传进行反序列化。因此,我们必须了解有关ViewState性能的内容。默认情况下,控件的ViewState将被启用,如果不需要使用ViewState,最好还是将它关闭。以下情况将不再需要ViewState:(1)控件未定义服务器端事件(这时的控件事件均为客户端事件且不参加回送的);(2)控件没有动态的或数据绑定的属性值。关闭视图状态的方法是将控件的EnableViewState的值设置为”false”,即EnableViewState=”false”。
默认情况下,视图状态的有关内容在编译运行发送给客户端时,读者将在页面的HTML代码中看到__VIEWSTATE隐藏域内容。这是一些没有意义的字符串,是.NET框架通过Base64位编码对相关内容编码的结果。它们是通过明文方式在客户端和服务器端之间往返传送。在某些情况下,例如涉及密码、账号、连接字符串等敏感内容时,使用默认方式是很不安全的。为此,.NET框架为ViewState提供了两种安全机制:
· 校验机制:
可以通过设置EnableViewStateMAC=”true”属性来指示.NET框架向ViewState数据中追加一个散列码(该散列码是一种SHA1类型,长度有160位,因此会严重影响执行性能)。在回传事件发生时,将重新建立该散列码,它必须和最初的散列码匹配。通过这种方式,能够有效检验ViewState是否在传送过程中能够被篡改。默认情况下,.NET框架使用SHA1算法来生成ViewState散列代码。此外,也可以通过在machine.config文件中设置<machineKey>来选择 MD5 算法,如下所示:<machineKey validation=”MD5″ />。MD5算法的性能要比SHA1算法好一些,但是同样不够安全。
· 加密机制
使用加密来保护ViewState字段中的实际数据值。首先,必须如上所述设置EnableViewStatMAC=”true”。然后,将machineKey validation类型设置为3DES,即<machineKey validationKey=”AutoGenerate” decryptionKey=”AutoGenerate” validation=”3DES” />,这指示ASP.NET使用3DES加密算法来加密ViewState值。
(4)视图状态分块
以上内容介绍了视图状态的一些基本知识。然而,可能部分读者会有些疑惑:如果在某些情况下,视图状态数据变得很大,那怎么办呢?这样显然会出现一些意想不到的后果。为此,ASP.NET 2.0新增了一种名为”视图状态分块”的功能。如果视图状态的数据量变得太大,视图状态分块自动将数据分成多个块区,并将这些数据放在多个隐藏形式的字段中。
若要启用视图状态分块,可将MaxPageStateFieldLength属性设置为在单个视图状态字段中允许的最大大小(以字节为单位)。当该页回发到服务器时,该页会在页初始化阶段分析视图状态字符串,并还原页中的属性信息。默认设置是-1,这表示不存在最大大小,不会将视图状态分成多个块区。
(5)优点和缺点
使用视图状态具有以下3个优点:一、耗费的服务器资源较少(与Application、Session相比)。因为,视图状态数据都写入了客户端计算机中。二、易于维护。默认情况下,.NET系统自动启用对控件状态数据的维护。三、增强的安全功能。视图状态中的值经过哈希计算和压缩,并且针对Unicode实现进行编码,其安全性要高于使用隐藏域。
使用视图状态具有以下3个缺点:一、性能注意事项。由于视图状态存储在页本身,因此如果存储较大的值,即使在视图状态分块的情况下,用户显示页和发送页时的速度仍然可能减慢。二、设备限制。移动设备可能没有足够的内存容量来存储大量的视图状态数据。因此,移动设备上的服务器控件时,将使用其他的实现方法。三、潜在的安全风险。视图状态存储在页上的一个或多个隐藏域中。虽然视图状态以哈希格式存储数据,但它可以被篡改。如果直接查看页输出源,可以看到隐藏域中的信息,这导致潜在的安全性问题。
典型应用
在利用ASP.NET 2.0技术进行服务器控件开发过程中,有很多方面可以用到视图状态。常见的是利用ViewState字典实现服务器控件属性。ViewState是System.Web.UI.StateBag类型-一个键/值对的字典,服务器控件的属性值可以存储在ViewState中。下面通过一个典型示例,说明ViewState的应用方法。
在自定义服务器控件LabelInViewState中,实现了两个属性Text和TextInViewState。前者使用私有变量创建,后者使用ViewState实现。它们都用于获取或者设置文本内容。自定义控件实现文件LabelInViewState.cs源代码如下所示。
using System;using System.Collections.Generic;
using System.ComponentModel;using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;namespace WebControlLibrary{
[DefaultProperty(\"Text\")]
[ToolboxData(\"<{0}:LabelInViewState runat=server></{0}:LabelInViewState>\")]
public class LabelInViewState : WebControl {
private string _text; //实现Text属性
public string Text {
get {
return (_text == null) ? string.Empty : _text;
}
set { _text = value; }
}
//使用ViewState实现TextInViewState属性
public string TextInViewState {
get {
String s = (String)ViewState[\"TextInViewState\"];
return ((s == null) ? String.Empty : s);
}
set { ViewState[\"TextInViewState\"] = value; }
}
// 重写RenderContents方法
protected override void RenderContents(HtmlTextWriter output) {
output.Write(“Text = “);
output.Write(Text);
output.Write(“<br/>”);
output.Write(“TextInViewState = “);
output.Write(TextInViewState);
}
}
}