ASP.NET ViewState

ViewState主要是用来维护页面状态的,正所周知,web是无状态的,Asp.Net页面也没有状态,所以在服务器的每个往返的过程中,都有实例化,执行,呈现和处理,如果每次都这样,势必会对服务器造成很大的压力,性能会很低,那么在Asp.Net采用了ViewState机制,对一些不需要重新改变状态的信息,可以保存带到ViewState,总之合理使用ViewState可以在某种程度上大大的提高访问的性能

但是有些是.net自动帮助我们做好的东西,比如注册的时候,填写完信息后,回发到服务器的时候,这些信息会被自动的保存到服务器中,这些并不是ViewState在启作用,是另外的一种机制

它是由 ASP.NET 页面框架管理的一个隐藏的窗体字段。当 ASP.NET 执行某个页面时,该页面上的 ViewState 值和所有控件将被收集并格式化成一个编码字符串,然后被分配给隐藏窗体字段的值属性(即 <input type=hidden>)。由于隐藏窗体字段是发送到客户端的页面的一部分,所以 ViewState 值被临时存储在客户端的浏览器中。如果客户端选择将该页面回传给服务器,则 ViewState 字符串也将被回传。

回传后,ASP.NET 页面框架将解析 ViewState 字符串,并为该页面和各个控件填充 ViewState 属性。然后,控件再使用 ViewState 数据将自己重新恢复为以前的状态。

ASP.NET ViewState设计的目的是为了持久化当前页面中对象的状态,以便在下次页面回发的时候还原页面的状态,下面两点需要注意:

1、             ViewState只需要在页面回发的时候才需要使用

2、             1前提下,只要初始化状态被修改了对象才需要持久化,才需要使用ViewState

1比较清楚,来谈第2点。以简单的Label控件为例,先来看一下它的Text属性的实现:

public virtual string Text
{
   
 get
    {
       
 object obj2 = this.ViewState["Text"];
       
 if (obj2 != null)
        {
           
 return (string) obj2;
        }
       
 return string.Empty;
    }
   
 set
    {
       
 if (this.HasControls())
        {
           
 this.Controls.Clear();
        }
       
 this.ViewState["Text"] = value;
    }
}

很显然Text属性的后端都是以ViewState为存储介质的,ASP.NET服务器端控件的很多属性都是以类似方式实现的。假设一个页面default.aspx里只有一个Label控件, <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>,当访问该页面时,Label控件发送到客户端浏览器的代码大概为 <span id="xxx_Label1">Label</span>,同时ViewState中也保存了一份Text属性的值,形式大概为<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTgwMzg2ODMxMQ9kFgJmD2QWAgIDD2QWAg......
到目前位置,ViewState并没有发挥作用(这里谈论的都是在ViewState Enable的情况下),额外的保存了Text属性的值在这里是多余的。假设该页面还有一个Button控件,点击该按钮页面回发,那么在次过程中ViewState就发挥作用了么?分析一下该页面回发过程中Label控件生命周期的某些过程。首先,回发后,Label控件依然要经历初始化阶段(Init),这个阶段要创建一个Label控件的实例,同时设置其Text属性的,因为Text属性后端是以ViewState为存储介质的,所以就相当于向ViewState里添加了一个值,接下来,因为是回发过程,所以控件还要LoadViewState,即加载前次访问该控件的状态值,下面是Label控件的实现:

protected override void LoadViewState(object savedState)

{

    if (savedState != null)

    {

        base.LoadViewState(savedState);

        string text = (string) this.ViewState["Text"];

        if (text != null)

        {

            this.Text = text;

        }

    }

}

因为在前一访问过程中ViewState中所保存的LabelText属性的状态值就是Label的初始值,所以导致了这里的LoadViewSate过程是多余的了,而且InitLoadViewState两个过程对Text属性都赋了相同的值。由此可见,即使在页面回发中,如果不需要对属性的初始值进行修改,那么持久化属性的值(即使用ViewState)也是没有意义的。而且会带来多余的资源浪费,如两次对Text属性的赋值,以及增加了ViewSate的体积所带来的多余的网络传输。

那么,什么情况下使用ViewSate是值得的呢?我们先来把前面例子中两次访问的过程理一下:

1.            第一次访问页面,Label控件初始化,设置Text属性的值,即向ViewState中添加了一个条目;

2.            页面发送前(Render前),控件SaveViewState,即ViewSate中的值序列化,保存到一个隐藏域中;

3.            页面发送,Label控件发送为相应的HTML标签,读取Text属性设置HTML标签的对象属性值,同时发送隐藏域及其值。对于LabeText属性来讲,相当于一份ViewState中的值发送了两份客户端拷贝;

4.            第二次回发访问,Label控件初始化,设置Text属性的值,即向ViewState中添加了一个条目;

5.            由于是回发访问,需经历LoadViewState过程,本例中即读取ViewStateText属性在上一次访问中的状态值,而这个值实际上等于过程1中设置的值,读取的值再次设置Text属性,

6.            第二次发送,重复过程23.

从过程1控件初始化,到过程6,第二次发送前SaveViewState,在这两个过程中间,如果不需改变Text属性的初始值,那么实际上就不需要使用ViewState。假设我们在过程12中间改变Text属性值,如在Page_Load中如此:

 protected void Page_Loadt(object sender, EventArgs e)
 {
  if (!IsPostBack)
  {
   Label1.Text = "Change Label's value";
  }
 }

 那么,尽管在回发时不能执行Label1.Text = "Change Label's value";语句,但由于ViewState的作用,第一次访问设置的值,在第二次回发访问后仍然会存在,即LabelText属性值为”Change Label's value“,而不是其初始值“Label”。这种情况下才是ViewState的用武之地。注意!IsPostBack的使用,否则你只是每次访问都进行赋值而已,并没有利用ViewState的好处。

由此,可以得出,在满足前面所述的两个条件时才应该使用ViewState。那么在现实应用中同时满足以上两个条件的情况下多么,也就是说我们需要使用ViewSate的时候多么?

很显然,满足这两个条件最大宗的情况就是数据绑定。而我认为ViewState的设计目的主要就是为了将必要的信息持久化在页面中,避免在两次访问中(确切的说不只两次,而是所有的回发访问中)都要进行数据绑定(而每次数据绑定往往意味着一次次的数据库访问)。例如用GridView绑定DataSource控件展现一个类表数据,在ViewSate Enable的情况下,页面第一次加载时进行数据绑定,在随后的回发访问中,如果仍是访问当前数据视图,即没有进行分页、排序操作等,DataSource不会再进行数据绑定,因为所有的信息都可以从ViewSate中获取,不需要再次访问数据库再次绑定数据控件了。而如果你将ViewState Disable掉,那么每次访问则都需要进行数据绑定了(可以通过SqlProfiler来捕捉SqlDataSource在两种情况下对数据库的访问情况)。这个场景可能最能说明ViewSate的设计初衷了。

然而在实际的应用中,上面的这种场景多么?在数据列表页面,往往没有除了分页排序等之外的回发操作(你放Grid的页面里有回发的按钮么?),而分页,排序操作所引起的回发显然是需要数据再绑定的。如果是这种场景,那么你就应该把这个页面或者把这个GridEnableView属性设为false了。这里讲点题外话,有人会说如果设成false,那Grid的分页信息、排序信息怎么传递给后续的回发访问呢?其实在ASP.NET 2.0中控件的状态管理被分为了两部分view statecontrol state。两者的区别是什么呢,那ASP.NET 1.x中的DataGrid控件来说,DataGrid的所有状态信息都保存在view state当中,但是这些信息所符合的view state应用场景是矛盾的,比如你的页面没有回发操作,你不必把所有数据缓存到view state里,这时你会把datagridenableviewstate属性设为false,但当你这么做后,datagrid的另一些功能如翻页、排序,就没法使用了,因为翻页排序的状态信息也是保存在view state中的,如pageindexsort asc/desc等。就类似于这种问题,ASP.NET 2.0中又引入了control statecontrol state的存储方式与view state相同,不同的地方在于它不会被disable掉。这样control state用来存储那些控件的功能性的,必需的信息。比如即使GridViewview state被禁止了,它的分页,排序等信息还是仍然正常工作的。

前端时间,一个同事在在GirlView控件中进行全选,然后进行删除操作,结果选上之后,执行删除的时候,GirlView的信息都没了,代码大概如下

If(!ispostback){

GirlViewBind();

}

//绑定

Public void GirlViewBind()

{

//绑定代码

}

//执行删除

………

结果很是郁闷,结果去掉If(!ispostback){}然后就好了,后来才知道原来是禁用ViewState,回传之后GirlView的信息都丢失了,当你去掉之后If(!ispostback){}之后相当于又重新实例化,绑定了,其实完全没必要……

禁用ViewState多种方法,不同方法对应着不同的禁用范围。除了以上例子中直接在服务器控件标签中设置“EnableViewState”属性为“false”,还可以在@Page指令中设置这个属性以达到页面级禁用ViewState的目的。禁用整个WEB应用程序ViewState,修改Web.config中相应元素的属性即可,最后,禁用服务器全局的ViewState,修改.Net Framework安装目录下的machine.config即可。当禁用了ViewState以后,控件还可以通过control state(控件状态)保存状态数据,该属性一般用于自定义控件。
说明:ViewState只在页面内有效,不能跨页面使用

关于 ViewState 还有三个值得注意的小问题。
  • 如果要使用 ViewState,则在 ASPX 页面中必须有一个服务器端窗体标记 (<form runat=server>)。窗体字段是必需的,这样包含 ViewState 信息的隐藏字段才能回传给服务器。而且,该窗体还必须是服务器端的窗体,这样在服务器上执行该页面时,ASP.NET 页面框架才能添加隐藏的字段。
  • 页面本身将 20 字节左右的信息保存在 ViewState 中,用于在回传时将 PostBack 数据和 ViewState 值分发给正确的控件。因此,即使该页面或应用程序禁用了 ViewState,仍可以在 ViewState 中看到少量的剩余字节。
  • 在页面不回传的情况下,可以通过省略服务器端的 <form> 标记来去除页面中的 ViewState。

总结

ASP.NET ViewState 是一种新的状态服务,可供开发人员基于每个用户来跟踪 UI 状态。ViewState 没有什么神秘之处,它只是利用了一个老的 Web 编程技巧:在一个隐藏的窗体字段中来回传递状态,并将它直接应用于页面处理框架中。但效果却非常好 - 在基于 Web 的窗体中只需编写并维护很少的代码。

用户可能并不总是需要它,但我想您在需要它的时候会发现,ViewState 是提供给页面开发人员的诸多 ASP.NET 新功能中非常令人满意的一种功能。

posted @ 2011-03-02 11:34  双魂人生  阅读(1830)  评论(7编辑  收藏  举报