状态管理 Application/Session/Cookie/viewstate/Cache/隐藏域/查询字符串的比较

Application/Session/Cookie/Viewstate/Cache的区别

1 :Session 的详细请查看:ASP.NET Session详解(转)

3.ApplicationApplication 这是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里面存放的 应该是访问较多,修改较少并且是全局至少大部分功能会使用的数据,例如计数器或者数据库连接串等。 

在ASP.Net中Application用法与ASP是一样的,几乎是没有什么说的,但是它多了两个特别有用的事件, Application_OnBeginRequest和Application_OnEndRequest。他们的和原来的 Application_OnStart和Application_OnEnd一样是放在global文件中的(注意这个文件在ASP中名字是 global.asa,在ASP.Net中是global.asax)。

注:这个事件,写不写On是一样的。如Application_End与Application_OnEnd是一样的

  Application_OnStart是在整个ASP.Net应用首先被触发的事件,也就是在一个虚拟目录中第一个ASP.Net程序执行时 触发,Application_OnEnd就正好相反,在整个应用停止时被触发(通常发生在服务器被重启/关机时)。 Application_OnRequestStart和Application_OnRequestEnd则是在每一个ASP.Net程序被请求时就发 生,也就是说客户访问一次一个ASP.Net程序,这两个事件就会被触发。我们可以从下面的程序看到他的应用.我们先建立一个global.asax,内 容如下:

<script language="C#" runat="server">

void Application_OnBeginRequest(Object sender, EventArgs E)
{
  Response.Write("Request is Starting...<br>");
}

void Application_OnEndRequest(Object sender, EventArgs E)
{
  Response.Write("Request is Ending...<br>");
}

</script>

然后将其放到本虚拟目录的根目录下,然后我们随便打开一个什么aspx文件
我们在global.asax中定义的语句Request is Starting...和Request is Ending...这个不是我们在这个文件中独加的,我们将会再任何一个ASP.Net文件中看到它的影子 。

 

ViewStateViewState 这是我们今天重点讨论的;实际上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 id="ViewStateTest" method="post" runat="server">
  <asp:Button ID="btnPostBack" Runat="server" Text="Post Back" Width="85px"></asp:Button>
  <br/>
  <asp:CheckBox ID="chkTest" Runat="server" Text="This is a check box"></asp:CheckBox>
  </form>
 </body>
  </html>

   这是用Vs.Net设计出来的一个简单的页面,里面包含了一个服务器端的按钮和一个CheckBox,然后我们在服务器端响应按钮的事件:

   private void btnPostBack_Click(object sender, System.EventArgs e)
   {
 [1] Response.Write( "ViewState :"+Request.Params["__VIEWSTATE"]+"<br/>" );
  
 [2] string decodeValue = Encoding.UTF8.GetString( Convert.FromBase64String( Request.Params["__VIEWSTATE"] ) );
    
 [3] Response.Write( "ViewState decode :"+decodeValue+"<br/>" );

 [4] object viewstate = (new LosFormatter()).Deserialize( Request.Params["__VIEWSTATE"] );
 
 [5] Response.Write( "ViewState Object :"+viewstate.GetType().Name );
   }

   为了方便看,我加上了行号;第一行我们把ViewState的值打出来,第二行是什么呢?实际上ViewState保存到客户端的一串字符串就是内部的 ViewState通过某种方式序列化之后再经过Base64编码得来的,所以我们把Base64编码的字符串反编码一次再打出来;至于第四行,我先不 说,先看执行结果:
   运行之后,页面上什么都没有,除了按钮和CheckBox(废话 :)),我们点击按钮,然后结果如下:

  [A]   ViewState :dDwxMjU2MDI5MTA3OztsPGNoa1Rlc3Q7Pj6Gg0Qzm+7gacYWcy0hnRCT9toOdA==
  [B]   ViewState decode:t<1256029107;;l>D3i s-! t
  [C]   ViewState Object :Triplet

   然后我们来分析这个结果,A中显示的就是ViewState传到客户端的值,B中显示的是通过Base64反编码之后的值,从这里面好像还是看不出什么,C中出现了一个:Triplet ?这是什么呢,我们回到上面的代码:
      object viewstate = (new LosFormatter()).Deserialize( Request.Params["__VIEWSTATE"] );

   注意我们使用了一个LosFormatter类,实际上这个类就是Asp.Net内部为ViewState提供序列化的类,它有两个方法,一个是 Serialize,就是序列化一个对象,一个是Deserialize,是反序列化,我们这里使用了反序列化的方法来把ViewState直接反序列化 成一个对象,然后把这个对象的类型打出来,这个对象就是:Triplet类型,实际上Asp.Net中页面保存的ViewState就是这个类型,我们先 分析一下LosFormater,再来细说.
   我们再回来看打出来的结果B:t<1256029107;;l>D3i s-!  
t, 实际上通过查看LosFormatter反编译后的代码,大致上可以看出它序列化的方式是很简单的,就是判断要序列化对象的类型,如果不是直接序列化的类 型,则把它的类型记录下来,然后在递归序列化它的属性,我们看B中的"t"就是表示Triplet这个类型,这个类型有三个属性,这三个属性包含在 "<"和">"之间,用";"分割,而最后面的D3i s-!  
t据我分析应该是一个防止ViewState被改变的Hash值,这个不是很确定,因为反编译的代码实在是很难看,我只是了解之后就没仔细看了。

   我们刚刚分析出来Page中的ViewState反序列化之后是Triplet这个类型,实际上这个类在MSDN中就查得到,它就是一个包含了三个对象的 对象,说简单点,它就是一个能放三个箱子的大箱子(好像还是说的比较糊涂,呵呵),它有三个属性:First、Second、Thrid :),分别代表三个对象。
   对应到Page当中,First是Page.GetTypeHashCode()的返回值,这个方法是System.Web.UI.Page定义的一个保 护的虚拟方法,返回一个整型,由Aspx文件生成的类来实现的,因为这个类是有Asp.Net负责在运行期生成源代码并编译,它会计算出一个大常量作为返 回值,这个返回值在整个Web应用程序所有的Page中是唯一的。(提一句题外话,Asp.Net自动产生的源代码可以到 系统盘:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\Temporary ASP.NET Files下面去找),这个唯一的Hash值是为了在ViewState中产生一个标记,使这个ViewState只适用与对应的页面。
   Second则是通Control.SaveViewStateRecursive方法递归保存页面控件树的ViewState返回的对象,也就是真正的ViewState的数据。
   Third中保存的是当前页面需要PostBack的控件名的列表。
  

   分析了页面的ViewState的构成,我们再来看Control的ViewState的实现。ViewState是 System.Web.UI.Control类实现的一个属性,这个属性的类型是System.Web.UI.StateBag,这个类就包含了 ViewState数据结构的实现,实际上它的内部也就是个Hash表,通过Key值来保存和检索数据。
  那么服务器控件是怎么实现保存状态的呢?
  我们知道,所有的服务器控件都是从System.Web.UI.Control派生的,所以都拥有ViewState这个属性,在Control内部,定义了两个Protected的虚拟方法:
    protected virtual object SaveViewState() 
  和
protected virtual void LoadViewState(object savedState)

  这两个方法是给子控件派生用来保存和读取自己的ViewState的,比如我们有一个自己写的控件,往ViewState中保存了一个字符串,那么我们的方法大致像这样:
    protected virtual object SaveViewState()
    {
       object[] states = new object[2];
       states[0] = base.SaveViewState();      //记得保存父控件的ViewState
       states[1] = "Hello,I'm timmy!";        //这里保存我们自己的
      
       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没有任何新意,但是结合服务器控件的设计还是很巧妙的。
   
   最后,以我个人的观点,我觉得ViewState的出现很大程度上减轻了程序员的负担,但是要看清的是ViewState的本质,合理的应用它。

posted on 2009-08-15 11:17  J.J.J  阅读(337)  评论(0编辑  收藏  举报