本文转载自dodream
视图状态是 ASP.NET 页框架用于在往返过程之间保留页和控件值的方法。在呈现页的 HTML 标记时,必须在回发过程中保留的页和值的当前状态将被序列化为Base64 编码字符串。然后,此信息将被放入一个或多个视图状态隐藏字段。
1、方案
视图状态由 ASP.NET 页框架自动用于保存在各个回发之间必须保留的信息。此信息包括控件的任何非默认值。
您还可以使用视图状态来存储特定于页的应用程序数据。
2、功能
视图状态是 ASP.NET 页中的存储库,可以存储必须在回发过程中保留的值。页框架使用视图状态在各个回发之间保存控件设置。
可以在您自己的应用程序中使用视图状态完成以下工作:
·在各个回发之间保存值,而不将这些值存储在会话状态或用户配置文件中。
·存储您定义的页或控件属性的值。
·创建一个自定义视图状态提供程序,以便将视图状态信息存储在 SQL Server数据库或其他数据存储区中。
例如,您可以将信息存储在视图状态中,这样在下次将该页发送到服务器时,代码便可以在页加载事件过程中访问这些信息。
3、背景
Web 应用程序是无状态的。每次从服务器请求页时,都会创建网页类的一个新实例。这通常意味着在每次往返过程中会丢失该页及其控件中的所有信息。例如,默认状态下,如果用户将信息输入到 HTML 网页上的文本框中,该信息会发送到服务器。但是,该信息不会在响应中返回到浏览器。
为了克服 Web 编程的这一固有的局限性,ASP.NET 页框架包含几种状态管理功能,可以在往返过程之间将页和控件值保存到 Web 服务器。其中一种功能便是视图状态。
默认情况下,ASP.NET 页框架使用视图状态在往返过程之间保存页和控件值。在呈现页的 HTML 时,必须在回发过程中保留的页和值的当前状态将被序列化为Base64 编码字符串。然后,它们将被放入页中的一个或多个隐藏字段。
您可以在代码中使用页的 ViewState 属性访问视图状态。ViewState 属性是一个包含键/值对(其中包含视图状态数据)的字典。
安全说明: 恶意用户可以很容易地查看和修改隐藏域的内容。
通过实现自定义的 PageStatePersister 类以存储页数据,您可以更改默认行为并将视图状态存储到另一个位置(如 SQL Server 数据库)。
4、使用视图状态的注意事项
视图状态提供了特定 ASP.NET 页的状态信息。如果需要在多个页上使用信息,或者如果需要在对网站的多次访问之间保存信息,必须使用其他方法来维护状态。您可以使用应用程序状态、会话状态或配置文件属性。
视图状态信息被序列化为 XML,然后使用 base-64 编码机制进行编码,这可能会生成大量数据。将页发送到服务器时,视图状态的内容会作为页回发信息的一部分进行发送。如果视图状态包含大量信息,则会影响页的性能。请使用应用程序的典型数据测试页性能,以确定视图状态的大小是否会导致性能问题。
如果不必存储单个控件的控件信息,则可以禁用控件的视图状态。如果页上的某个控件在每次回发时从数据存储区进行刷新,则可以关闭该控件的视图状态以减少视图状态的大小。例如,可以关闭 GridView 控件的视图状态。
说明: 即使您显式关闭视图状态,也仍会将隐藏字段发送到浏览器,以指示该页正在回发信息。
另一个注意事项是,如果隐藏字段中的数据量过大,则某些代理和防火墙将禁止访问包含这些数据的页。由于最大允许数量可能随所实现的防火墙和代理的不同而不同,因此大的隐藏字段可能会导致间歇性问题。如果存储在 ViewState 属性中的数据量超出了在页的 MaxPageStateFieldLength 属性中指定的值,则页会将视图状态拆分为多个隐藏字段。这可减小单个隐藏字段的大小,使其大小不会超过防火墙所允许的上限。
某些移动设备根本不允许使用隐藏字段。因此,视图状态对于这些设备无效。
5、控件状态
除视图状态以外,ASP.NET 还支持控件状态。页使用控件状态来保留必须在回发之间保留的控件信息,即使已为页或某个控件禁用视图状态也是如此。控件状态与视图状态类似,也存储在一个或多个隐藏字段中。
6、将值保存在视图状态中
您可以使用页的 ViewState 属性(该属性公开一个字典对象)来访问视图状态信息。可以使用此字典存储自定义值。一个典型用法是存储您在页中定义的自定义属性的值。
由于视图状态是作为隐藏字段发送的,因此直到该页的 PreRenderComplete 事件发生之前,都可以对视图状态进行更改。在将页呈现到浏览器之后,便无法保存对视图状态所做的更改。
如果用户查看网页的源并可以对 base-64 编码字符串进行解码,则可以看到隐藏视图状态字段中的信息。这可能会产生安全问题。
说明: 若要使用 ViewState 属性 (Property),ASP.NET 网页必须有一个具有runat="server" 属性 (Attribute) 的 form 元素。
若要将值保存到视图状态,请创建一个包含要保存的值的新项,并将该项添加到视图状态字典中。下面的示例演示一个 ASP.NET 网页,该网页包含用于将一个字符串和一个整数值保存在视图状态中的代码。
<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
' Sample ArrayList for the page.
Dim PageArrayList As ArrayList
Function CreateArray() As ArrayList
' Create a sample ArrayList.
Dim result As ArrayList
result = New ArrayList(4)
result.Add("item 1")
result.Add("item 2")
result.Add("item 3")
result.Add("item 4")
Return result
End Function
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
If (Me.ViewState("arrayListInViewState") IsNot Nothing) Then
PageArrayList = CType(Me.ViewState("arrayListInViewState"), ArrayList)
Else
' ArrayList isn't in view state, so it must be created and populated.
PageArrayList = CreateArray()
End If
' Code that uses PageArrayList.
End Sub
Sub Page_PreRender(ByVal sender As Object, ByVal e As EventArgs)
' Save PageArrayList before the page is rendered.
Me.ViewState.Add("arrayListInViewState", PageArrayList)
End Sub
</script>
<html >
<head runat="server">
<title>View state sample</title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
// Sample ArrayList for the page.
ArrayList PageArrayList;
ArrayList CreateArray()
{
// Create a sample ArrayList.
ArrayList result = new ArrayList(4);
result.Add("item 1");
result.Add("item 2");
result.Add("item 3");
result.Add("item 4");
return result;
}
void Page_Load(object sender, EventArgs e)
{
if (ViewState["arrayListInViewState"] != null)
{
PageArrayList = (ArrayList)ViewState["arrayListInViewState"];
}
else
{
// ArrayList isn't in view state, so it must be created and populated.
PageArrayList = CreateArray();
}
// Code that uses PageArrayList.
}
void Page_PreRender(object sender, EventArgs e)
{
// Save PageArrayList before the page is rendered.
ViewState.Add("arrayListInViewState", PageArrayList);
}
</script>
<html >
<head runat="server">
<title>View state sample</title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
7、可以存储在视图状态中的数据类型
您可以将下列类型的对象存储到视图状态中:
字符串、整数、Boolean 值、Array 对象、ArrayList 对象、哈希表、自定义类型转换器
您还可以存储其他类型的数据,但是必须使用 Serializable 属性编译类,以便可以为视图状态序列化该类的值。
8、从视图状态读取值
若要从视图状态读取值,请获取页的 ViewState 属性,然后从视图状态字典中读取值。
下面的示例演示如何从视图状态中获取一个名为 arrayListInViewState 的 ArrayList对象,然后将一个 GridView 控件作为数据源绑定到该对象。
Dim arrayList As ArrayList
arrayList = CType(ViewState("arrayListInViewState"), ArrayList)
Me.GridView1.DataSource = arrayList
Me.GridView1.DataBind()
arrayList = new ArrayList();
arrayList = (ArrayList)ViewState["arrayListInViewState"];
this.GridView1.DataSource = arrayList;
this.GridView1.DataBind();
视图状态中的值被类型化为 String。在 Visual Basic 中,如果设置了 Option Strict On,则必须在使用视图状态值之前将这些值强制转换为适当的类型,如上面的示例中所示。在 C# 中,当您读取视图状态值时,必须始终将其强制转换为适当的类型。
如果尝试从不存在的视图状态中获取值,则不会引发任何异常。若要确保值在视图状态中,请首先检查对象是否存在。下面的示例演示如何检查视图状态项。
if (ViewState["color"] == null)
// No such value in view state, take appropriate action.
如果您尝试通过其他某种方式使用不存在的视图状态项(如检查其类型),则会引发 NullReferenceException 异常。
9、保证视图状态的安全
默认情况下,视图状态数据存储在页上的隐藏字段中,并使用 Base64 编码机制进行编码。此外,还会使用计算机身份验证代码 (MAC) 密钥从视图状态数据中创建这些数据的哈希。该哈希值会添加到编码的视图状态数据中,并且生成的字符串会存储在页中。当页被回发到服务器时,ASP.NET 页框架会重新计算哈希值,并将其与视图状态中存储的值进行比较。如果哈希值不匹配,将引发异常,指示视图状态数据可能无效。
通过创建哈希值,ASP.NET 页框架可以测试视图状态数据是否已被损坏或篡改。但是,即使视图状态数据未被篡改,这些数据仍然可能被恶意用户截获和读取。
10、使用 MAC 计算视图状态哈希值
用于计算视图状态哈希值的 MAC 密钥可以自动生成,也可以在 Machine.config文件中指定。如果该密钥是自动生成的,则基于计算机的 MAC 地址(它是该计算机中网络适配器的唯一 GUID 值)进行创建。
恶意用户很难根据视图状态中的哈希值进行反向工程处理以推断出 MAC 密钥。因此,MAC 编码是一种用来确定视图状态数据是否已更改的相当可靠的方式。
通常,用于生成哈希的 MAC 密钥越大,不同字符串的哈希值相同的可能性就越小。如果密钥是自动生成的,则 ASP.NET 使用 SHA-1 编码来创建一个大型密钥。不过,在网络场环境中,所有服务器的密钥必须相同。如果密钥不同,那么当页回发至创建该页的服务器之外的其他服务器时,ASP.NET 页框架将引发异常。因此,在网络场环境中,应在 Machine.config 文件中指定密钥,而不是让 ASP.NET 自动生成密钥。在这种情况下,请确保您创建的密钥足够长,以便使哈希值具有充分的安全性。但是,密钥越长,创建哈希所需要的时间也就越多。因此,必须在安全需求与性能需求之间进行权衡。
11、加密视图状态
虽然 MAC 编码有助于防止篡改视图状态数据,但这种编码也会妨碍用户查看数据。可以通过下面两种方式来防止他人查看此数据:通过 SSL 传输页,以及加密视图状态数据。要求通过 SSL 发送页有助于防止那些原本不应该收到该页的人探查数据包和未经授权访问数据。
但是,请求页的用户仍然能够查看视图状态数据,因为 SSL 会解密页以便在浏览器中进行显示。如果您不担心授权用户可以访问视图状态数据,则这种方法很好。但在某些情况下,控件可能会使用视图状态存储任何用户都不应访问的信息。例如,页可能包含一个数据绑定控件,该控件存储视图状态的项标识符(数据密钥)。如果这些标识符中包含敏感数据(如客户 ID),则应对视图状态数据进行加密来替代通过 SSL 发送页,或是将其作为通过 SSL 发送页的补充方法。
若要加密数据,请将页的 ViewStateEncryptionMode 属性设置为 true。在视图状态中存储信息时,可以使用常规的读写技术;页会为您处理所有加密和解密工作。对视图状态数据进行加密可能会影响应用程序的性能。因此,如不需要,请不要使用加密。
12、控件状态加密
使用控件状态的控件可以通过调用 RegisterRequiresViewStateEncryption 方法来要求对视图状态进行加密。如果页中的任何控件都要求对视图状态进行加密,则该页中的所有视图状态都会进行加密。
13、基于每个用户的视图状态编码
如果网站需要对用户进行身份验证,则可以设置 Page_Init 事件处理程序中的ViewStateUserKey 属性,以便将页的视图状态与特定用户相关联。这将有助于防止一键式 (one-click) 攻击,在这种方式的攻击中,恶意用户创建一个有效的预先填充的网页,该网页具有来自以前创建的网页的视图状态。攻击者随后引诱受害者单击一个链接,该链接使用受害者的标识向服务器发送页。
如果设置了 ViewStateUserKey 属性,将使用攻击者的标识来创建原始页的视图状态的哈希。受害者被引诱重新发送此页时,由于用户密钥不同,因此哈希值也将不同。这样,页的验证将失败,并且引发一个异常。
必须将 ViewStateUserKey 属性与每个用户的一个唯一值(如用户名或标识符)相关联。
14、在共享宿主环境中保护配置的安全性
在共享的宿主环境中,恶意用户可能会修改状态管理属性,从而可能影响到计算机上的其他应用程序。修改方式包括:直接修改 Machine.config;使用配置类进行修改;以及使用其他管理和配置工具进行修改。您可以通过对配置文件的节进行加密来帮助防止他人修改您的应用程序配置。