HiddenField/ViewState/ControlState__保存数据的另一个场所
HiddenField(隐藏域):
Session、Application和Cache都是保存在服务器内存中的。一般来说我们是无权访问客户端的机器,把数据直接保存在客户端的(Cookie是一个例外,不过Cookie只能保存不超过4K的字符串)。我们可以想一下还有哪里可以让我们暂时保存数据的?那就是页面!如果我们在Web页面中放置一个Label控件,然后设置它隐藏。那么我们就可以使用这个Label来保存一些临时数据,供当前页面的程序使用。
在ASP.NET中,我们还可以使用隐藏域来进行类似的工作,和Label不同的是,在隐藏域中填写的内容不会直接显示在IDE的设计视图中。由于我们保存的这些数据根本不需要显示给用户看,所以用隐藏域更合理一些。
<asp:HiddenField ID="HiddenField1" runat="server" Value="编程快乐" />
在代码中可以直接访问隐藏域的Value属性获得其值。
Response.Write(HiddenField1.Value);
不过,这样做还有几个不合理的地方:
-
数据直接暴露给用户。
-
只能存储字符串数据。
ViewState:
ASP.NET引入了ViewState(视图状态)的概念。从这个名字上我们大概可以体会出,ViewState主要是用来存放和视图有关的一些状态。比如,在用户注册时用户填写了一大堆数据,提交页面后系统返回了一个“用户名重复”的出错信息,此时先前用户在页面上填写的一些注册资料全部没有了。用户会是什么感觉呢?我想大多数用户会很恼火。ASP.NET通过ViewState自动保存控件的状态。你可能也发现了,文本框中的数据在页面提交后还是存在的。
同时,我们也可以利用ViewState来保存一些程序需要的数据。ViewState中的数据默认是使用base64进行编码的,因此,用户不能直接看到里面的数据。我们在代码中可以这样添加一个ViewState项:
ViewState["test"] = "编程快乐";
打开页面,观察源代码,ViewState就在这里:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="XT+Q3cCGrb+qjUKNB1N7x CMUgAMjbpmAwtMtwPE+b5Ii8uRFaO42AgKyR+u9T0Be" />
既然ViewState是存在页面上的,那么ViewState肯定是不能跨页面使用的,而且每个用户访问到的ViewState都是独立的。此外,ViewState也没有什么生命周期的概念,页面在ViewState就在,页面关闭了ViewState就关闭了。
观察上面的ViewState,是不是找不到“编程快乐”这几个字的影子呢?请在页面上随便加入一个按钮,按钮的Click事件处理方法如下:
Response.Write(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(
Request["__VIEWSTATE"])));
我们对ViewState数据进行base64解码后就能看到“编程快乐”的字样了。不过,现在的那串字符串还是很乱。其实,ASP.NET首先对ViewState中的数据进行序列化,然后再使用base64编码后存储在页面的隐藏域中。base64不是什么加密算法,只是一种编码算法,任何人都能对base64进行反编码。
ViewState的安全与性能:
如果我们需要在ViewState中保存一些相对比较机密的数据(当然,非常机密的数据不建议你保存在ViewState中),又如何保证ViewState的安全性呢?一般来说可以从两个方面入手。
-
保证客户端提交过来的ViewState没有被修改。我们做Web应用程序,心中要有这样一个意识,那就是客户端的一切都是不可相信的。大家可能以为只有我们提供了诸如TextBox等控件,用户才能修改。其实这种观点是错误的,虽然DropDownList中的内容只允许选择不允许修改,但完全可以伪造一个页面进行提交。对于ViewState也是同样道理,为了进一步的安全,我们需要验证客户端发回的ViewState是否已经被修改了。
-
保证用户不能直接看到ViewState中的数据。说白了就是对ViewState进行加密。在ASP.NET 2.0中,我们只需要进行简单地配置就能对ViewState进行验证和加密,在页面头部添加EnableViewStateMac(验证)和ViewStateEncryptionMode(加密)属性:<%@ Page Language="C#" … EnableViewStateMac="true" ViewStateEncryptionMode="Always" %>当然,如果你希望为所有页面的ViewState应用验证和加密,可以在Web.config的system.Web节点中添加:<pages enableViewStateMac="true" viewStateEncryptionMode="Always"></pages>
既然ViewState中的数据是序列化后加入的,那么我们就可以把一些复杂的类型也存放到ViewState中。在ViewState中保存自定义类型同样需要为类型标记[Serializable],那么在这里我们使用ViewState保存MyUser实例的代码就和使用Session差不多。
MyUser user = new MyUser();
user.sUserName = "ZZH";
user.iAage = 24;
ViewState["CustomClass"] = user;
读取代码:
MyUser user = ViewState["CustomClass"] as MyUser;
Response.Write(user.ToString());
那么,ViewState中能保存多少数据呢?暂且不说表单Post的数据是有大小上限的,ViewState是经过序列化和编码后保存在页面中的。如果我们在ViewState中保存一个拥有100条记录的DataSet,恐怕页面就很难打开了。不信的话,下载下面的文档里面有说明。而且这些数据还要在浏览器和服务器之间往返,占用的网络流量很客观。因此,笔者建议你在ViewState中保存尽量少的数据。如果实在需要在ViewStatge中放置大量数据建议使用maxPageState- FieldLength对ViewState启用分块传输。
<%@ Page Language="C#" … maxPageStateFieldLength="100"%>
我们知道,ViewState不仅仅是我们在使用,ASP.NET会把控件交互相关的一些数据都存放到ViewState中,但是对于一些不实现任何交互的控件(比如显示10条记录的GridView),你可以设置控件的EnableViewState属性为false来让控件不使用ViewState,从而减少页面体积。
ControlState:
最后,我们再简单提一下,ASP.NET 2.0提供了ControlState。它用于保存(自定义)控件的关键信息。就算页面或者控件的ViewState被关闭它还能起作用,弥补了ViewState能被禁止的不足。不过使用ControlState稍显复杂,我们需要自己序列化复杂对象进行存储。下面的代码演示了如何在ControlState中保存和读取简单字符串:
PageStatePersister.ControlState = "编程快乐";
Response.Write(PageStatePersister.ControlState.ToString());
总结
其实隐藏域、ViewState和ControlState的原理差不多,我们来总结一下。
· 存储的物理位置。表单隐藏域。
· 存储的类型限制。可序列化类型(直接在隐藏域中保存内容需要自己序列化)。
· 状态使用的范围。当前页面(当前控件),对用户独立。
· 存储的大小限制。存储过大数据会导致页面不能正常打开,不能正常提交。
· 生命周期。页面在就在页面不在也就不在了。三者始终是依附在页面的隐藏域中的。
· 安全与性能。在客户端存储,安全性低。不过,ViewState提供了验证和加密。
优缺点与注意事项
易于存储少量数据非常方便简单。但需要注意不要存储敏感数据,不要存储过大的数据。它们和前面说的Cookie、Session与Application不同。虽然Cookie也是存储在客户端,每次提交都附加在HTTP头中进行提交,但是它的数据量毕竟不大,起了一个标记的作用。Session和Application都是存储在服务器端的,不会参与页面往返过程。隐藏域、ViewState和ControlState始终参与往返,而且序列化和反序列化会消耗一定资源,因此,存储过大的数据会导致网页加载过慢,浪费服务器带宽。