ViewState是.Net中提出的状态保存的一种新途径(实际上也是老瓶装新酒);我们知道,传统的Web程序保存状态的方式有这样几种:
1、Application 这是Web应用程序生命期中的全局保存区,保存在Application中的数据是全局有效的;在Asp.Net中,有一个应用程序池,其中保存了数个 (或数十个)应用程序实例,每一次请求都会从池中取一个实例来处理请求,在请求完毕之前,这个实例不会接受其他请求;这就出现一个问题,同一时间可能存在 多个应用程序,也就是多个线程,这些线程都存在访问Application的可能,所以在对Application中的对象进行处理的时候需要考虑线程同 步的问题;实际上Application对象内部实现了一个线程锁,调用它本身的Add、Remove等方法的时候会自动调用加锁和解锁的操作,但是出于 性能考虑,对于直接通过索引器或其他方式得到其中的对象并进行操作的过程,Application并没有自动处理线程同步,需要利用下列类似的代码来处 理:Application.Lock();
((int)Application["Count"])++;
Application.Unlock();
值得注意的是,调用了Lock之后,如果没有显示的调用Unlock,那么在这个请求结束的时候,Application对象会自动解锁,这样防止了造成死锁的问题,但是为了代码的健壮性,调用完Lock并且修改完毕应该立即的调用Unlock方法。
Application 对象本质上就是一个Hash表,按照键值存放了对象,由于对象是全局并且存放在服务器,并且存在多线程同时访问,所以,Application里面存放的 应该是访问较多,修改较少并且是全局至少大部分功能会使用的数据,例如计数器或者数据库连接串等。
2、Session 在Asp.Net内部,有一个StateApplication来管理Session,实际上就是一个辅助进程,处理Session到期、创建的特殊请 求,在收到每一次请求的时候,辅助进程就会调用状态服务器(可以通过Web.config设置不同的状态服务器)来获取Session,如果没有对应该 SessionId的Session,则会新建一个,然后绑定到上下文中(HttpContext);与Asp不同的是,Session的状态服务器有多 种,目前在Asp.Net内部实现了三种:
1) InProcStateClientManager 这是传统的Session保存方式,但是还是有些细微差别
2) SqlStateClientManager 这是将Session保存到数据库方式
3) OutOfProcStateClientManager 这是将Session保存到进程外的方式
Asp.Net的Session机制有一个特点,就是处理Session的辅助进程与保存Session的状态服务器是分开的,按照MSDN的说法,有下列好处:
“因为用于会话状态的内存不在 ASP.NET 辅助进程中,所以可以实现从应用程序故障的恢复。”
“因为所有状态与辅助进程不存储在一起,您可以干净地跨多个进程对应用程序进行分区。这种分区可以显著地提高多个进程的计算机上应用程序的可用性和可缩放性。”
“因为所有状态与辅助进程不存储在一起,所以您可以跨运行于多个计算机上的多个辅助进程对应用程序进行分区。”
Asp.Net的Session机制个人观点,感觉灵活性比较好,内部实现也比较巧妙,但是实际上因为没有做过多的测试,所以应用上会不会像它说的那么美好,不敢打包票。有机会,我会单独写篇文章来深入的探讨Asp.Net 内部的Session机制。
3、Cookie 这个没甚么好说,实际上Asp.Net与Asp的Cookie没甚么分别,也许这项技术毁誉参半,而且比较依赖客户机实现,MS也没什么改进的。
4、ViewState 这是我们今天重点讨论的;实际上ViewState并不神秘,就是一个Hidden字段,但是它是服务器控件状态保存的基础;不熟悉的朋友可以用IE查看 Html源码,找到一个名为"__VIEWSTATE"的Hidden字段,其中有一大堆乱七八糟的字符,这就是页面的ViewState。
做过Web程序的人可能都有这种痛苦的体会,有时候为了处理页面上面比较复杂的功能,常常会加很多Hidden,然后在服务器端用一大堆判断来分析目前的 状态,写起来烦人,写完了代码更是难看;实际上,ViewState就是帮我们系统的实现了保存控件状态的功能,服务器端控件能够在多次请求间保存状态也 全靠它。
好,介绍就到这里,今天我们不是讨论ViewState的使用,而是从内部来探探这个东西的本质。
我们首先建一个测试的页面:
<%@ Page language="c#" Codebehind="ViewStateTest.aspx.cs" AutoEventWireup="false" Inherits="CsdnTest.ViewStateTest" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>ViewStateTest</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body>
<form ; //这里保存我们自己的
return states; //返回重新包装后的保存对象
}
获取的时候:
protected override void LoadViewState(object savedState) //这里的savedState就是我们Save的时候return 的object数组
{
object[] states = (object[])savedState;
base.LoadViewState( states[0] ); //把父类的数据给他自己去解析
string myData = (string)states[1]; //获取我们自己的数据
}
我们可以按照自己的方式来保存,不一定非要像上面这样用数组,实际上我们可以用任何支持序列化的对象都可以,父类并不关心子类如何保存,我们只要在Save和Load的时候使用同样的方式,并且把正确的数据传递给父类方法就可以了。
另外,还有一个问题就是我们使用的Control的ViewState是Key-Value这样的键值对,那它是怎么保存的呢?
实际上很简单,System.UI.Web下面有一个类叫Pair,呵呵,这个和Triplet差不多,只是它里面只有两个对象。StateBag保存的时候,First会存放所有Key值的数组,Second则存放所有Value的数组。
到现在,我们了解了ViewState是如何序列化并且保存到客户端,也了解了控件怎么保存自己的ViewState,那么这二者是怎么结合的呢?
也就是整个页面的控件树的ViewState是怎么保存和读取的呢?
在Control内部有两个internal的方法:
internal object SaveViewStateRecursive();
internal void LoadRecursive();
这两个方法由System.Web.UI.Page来调用,Page在Render结束后就会调用SavePageViewState方 法,SavePageViewState方法会调用Control的SaveViewStateRecursive()方法,这个方法就是通过递归调用每 一个Control.Controls的SaveViewStateRecursive方法来保存控件树中所有控件的ViewState。到这里,可能聪 明的朋友要问了,既然SaveViewStateRecursive是递归调用保存的方法,那么我们上面写的SaveViewState()方法又有什么 用呢?
我们知道,Control.Controls可能会有很多个,而且我们的SaveViewState()只保存了当前控件的数据,而没有记录控件树的结 构,那么如果我们递归SaveViewState()方法来保存数据的话,那么控件树的结构就会丢失,那么Load的时候就没办法还原了,实际上在 SaveViewStateRecursive方法中大致的代码是这样:
[1] 获取控件自己的ViewState(调用SaveViewState方法)
[2] 循环子控件
{
定义两个动态数组,一个保存控件的索引,一个保存递归调用子控件SaveViewStateRecursive方法返回的值
}
[3] 定义一个Triplet(呵呵,这个东西又出现了)
[4] First保存本控件的ViewState
[5] Second保存子控件的索引
[6] Third保存递归子控件SaveViewStateRecursive方法的返回值
[7] 返回Triplet
这样就保存了整个控件树的ViewState和控件树的结构
Load的方式与Save差不多,只是Load的时候会从savedState中获取子控件的索引来依次递归子控件的LoadRecursive()方法,这样才能保证正确的把保存的数据传给子控件。
到这里,ViewState的实现我们大致了解了一下,最后得出一些结论:
1、ViewState是存放在客户端,因此会减轻服务器的负担,是一种比较好的保存数据的方式。
2、因为ViewState本身的限制,只能保存可以序列化的对象,而且最好不要放太多东西,能省则省,以免在减慢传输的速度,以及加重服务器解析的负担。
3、我们通过很简单的方式就可以把ViewState里面的值获取出来,我们上面讨论了一些,虽然没有把解析的代码写出来,但是利用 LosFormatter可以得到ViewState反序列化后的对象,那么要解析出来简直是易如反掌;所以ViewState在安全性上面还是比较差, 建议不要
存放比较机密和敏感的信息,尽管ViewState可以加密,但是由于ViewState要保存在客户端,天生就有安全性的隐患。
4、实际从技术角度,ViewState没有任何新意,但是结合服务器控件的设计还是很巧妙的。
文章来源:http://www.cnblogs.com/purplefox2008/archive/2009/09/20/1570494.html