65.9K
CodeProject 正在变化。 阅读更多。
Home

关于 ASP.NET ViewState 的常见误解

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.75/5 (6投票s)

2007 年 2 月 20 日

9分钟阅读

viewsIcon

45096

描述 ASP.NET ViewState 的幕后发生的事情

引言


我确信在阅读本文后,大多数关于视图 状态的问题都将得到解决。
。如果你禁用控件的 View State,90% 的控件功能将无法使用。
在正确的位置初始化控件将解决大多数问题。我从 David Creed 在 ASP.NET 论坛中关于 ViewState 的文章中获得灵感
编写本 FAQ。感谢他撰写如此精彩的内容 :)。我已将他的想法转换为
FAQ,这更易于阅读,并针对开发人员日常面临的问题
问题。


1) 我们在 ASPX 或 .ascx 文件中声明的任何属性值都会进入 ViewState 吗?这个说法正确吗?
所有你分配给服务器控件的静态数据都不会进入视图 状态。常见的
误解是输入到控件属性中的任何内容都会进入视图状态

例如,如果我有两个 Label 控件

<asp:Label id="label1" runat="server" Text="abc" />

一个 Text="abc",另一个 Text="1234"。在这两种情况下,ViewState 都不会
包含 Text 字段的值。任何控件都是如此。这里我谈论的是
Label 的 View State 和因此 视图 状态包含 Label 的控件的。


现在,如果你在 Page load 中输入 Label 的 Text 属性
label1.Text="Abc";
那将会进入声明 Label 的控件的 ViewState。所以总而言之,在 Page_load 中分配
控件的初始值不必要地增加了视图 状态的负担。我将
在后面的 FAQ 中解释原因,我在其中讨论了视图 状态跟踪。
犯这样的错误很容易,一旦你知道正确的方法,修复起来就非常
简单了。


2) 如上例所述,如果 Viewstate 未存储在控件中,那么如何
任何控件在回发时跟踪其值?

首先,这是一个常见的误解,即如果我设置 EnableViewState=False,控件将不记住其回发
数据。让我们举个例子。假设我在页面中声明一个
DropDownList,如下所示

<asp:DropdownList id="lstStates" runat="server" DataTextField="StateName"
 
DataValueField="StateCode" EnableViewState="False"/>
  
protected override void OnLoad(EventArgs args) {
 


if(!this.IsPostBack)
 {
   this.lstStates.DataSource = QueryDatabase(); 
 
   this.lstStates.DataBind(); 
}
base.OnLoad(e); 
}

上述代码从性能角度来看是非常错误的。幕后发生的是你正在来回发送
整个状态列表 通过导线 在 View 中

状态
.

为了解决这个问题,我们可以将 Dropdown List 的 EnableViewState=false 并摆脱!
this.IsPostBack 并一直重新绑定。这比仅在
没有回发时才做更便宜。

等等!!!!现在,如果我像下面这样编写代码,SelectedValue 属性将在
Page_load 中重置回默认值

<asp:DropdownList id="lstStates" runat="server" DataTextField="StateName" 
DataValueField="StateCode" EnableViewState="False"/> 
  
protected override void OnLoad(EventArgs args) { 
 
this.lstStates.DataSource = QueryDatabase(); 
 
this.lstStates.DataBind(); 
  
base.OnLoad(e); 
 
}

有了上面的代码,我们不再将状态列表保存在 View State 中,但现在下拉列表
不记住它的 PostBackValue。发生这种情况是因为我们重新绑定后,所有
回发数据都消失了,Selected Value 属性具有默认值。

那么解决方案是什么?

解决方案是 PostBackData 在 Page_Load 之前和 OnInit 之后加载,所以如果我在填充回发数据之前绑定
控件,你仍然可以获得 Selected Value。

因此,以下代码将解决问题。

protected override void OnInit(EventArgs args) { 
 
this.lstStates.DataSource = QueryDatabase(); 
 
this.lstStates.DataBind(); 
  
base.OnInit(e); 
 
}

这个解决方案将很好地工作。现在在 Page_Load 中,即使我们一次又一次地重新绑定,你也会看到来自 Post Back 的正确 Posted Values。
Back。在这个解决方案中,View state 不会存储
国家列表,同时你正在获取回发数据。所以这是一个双赢的
局面。请记住,如果 EnableViewState=False,SelectedINdexChanged 事件将不起作用。

3) 在上面的例子中,如果我有一个下拉列表,并且我在
Page Load 中一直重新绑定,并且也保持 EnableViewState=true,为什么我没有得到
SelectedINdexChanged 事件?

这是代码中非常常见的问题。请将所有绑定移到 Page_init 中,在回发
数据加载之前,这样应该就没问题了。

4) 什么是 ViewState 跟踪?ASP.NET 如何记住更改,如果它不
存储在 ASPX 文件中定义的初始值,如第 1 项中所述?

视图 状态有一个名为 TrackViewState() 的方法。一旦为控件调用此方法,
之后所做的任何更改都会在 View State 中作为隐藏表单字段进行跟踪,你可以在
查看源窗口中一直看到。在页面生命周期中,Page 或 Control 有两个方法
被调用。Page 在回发期间在 Page_load 之前调用 LoadViewState,它在将数据序列化到隐藏表单字段之前调用 SaveViewState。
Page 的 SaveViewState() 调用其中所有子控件的 SaveViewState。在这一点上,只有在 View State 包中标记为“脏”的数据才会被序列化。现在的问题是框架在哪里调用 TrackViewState?答案在以下段落中。
Page 的 SaveViewState() 调用其中所有子控件的 SaveViewState。在这一点上,只有在 View State Bag 中标记为脏的数据才会被序列化。现在的问题是框架在哪里调用 TrackViewState?答案在以下段落中
Page 的 SaveViewState() 调用其中所有子控件的 SaveViewState。在这一点上,只有在 View State 包中标记为“脏”的数据才会被序列化。现在的问题是框架在哪里调用 TrackViewState?答案在以下段落中
Page 的 SaveViewState() 调用其中所有子控件的 SaveViewState。此时,只有在 View State Bag 中标记为脏的数据才会被序列化。现在的问题是框架在哪里调用 TrackViewState?答案在以下段落中
paragraph

假设你有这段代码

ViewState["key"]="abc"; 
 ViewState["Key']="yzs";// Till this moment these values will not go 
to Hidden Form Field. 
 ViewState.TrackViewState();// 
 ViewState["Key']="hhghjg" ;// this will be tracked and Will go 
//to Hidden Form Field. 

你无需在控件中明确调用 TrackViewState。它在控件的 OnInit 阶段
自动完成。因此,如果你在控件的 OnInit 阶段设置任何属性,你可以避免这些值被跟踪到 View State 中。之后,你设置的任何
属性都将进入该控件的 ViewState 并被跟踪。
属性都将进入该控件的 ViewState 并被跟踪。

让我们假设我有一个用户控件,其中包含 Label lblDate。在用户控件的 OnINit() 事件中,
我有以下代码

protected override void OnInit(EventArgs args) { 
 
this.lblDate.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); 
 
base.OnInit(e); 
 
}

这是否意味着 Label 的 Text 值不会进入 ViewState,因为我已经在我的用户控件的 ONINit 中设置了
Label 的 Text 属性?如果你认为上面的代码是
正确的,那么你就错了。子控件(Label)的 OnInit 在用户控件的 OnInit 调用之前已经调用,所以它的 ViewState 已经
被跟踪了。

你可能会问,我为什么不能只禁用 Label 的 ViewState 来摆脱所有
问题呢?那样会奏效,但是假设你有一个情况,点击一个按钮
它执行回发,并且按钮点击处理程序更改 Label Text,如下所示

private void cmdRemoveDate_Click(object sender, EventArgs args) { 
 
this.lblDate.Text = "--/--/---- --:--:--"; 
 
} 

你可能会认为在回发期间,我会在按钮
点击后看到空的日期,因为 Label 中没有启用 View State,我没有将默认
值(即当前日期)保存在 View State 中。嗯!!你又错了?答案是,如果你禁用
View State,那么在回发时,你会再次看到当前日期,这不是你
想要的。你想要的是不将原始值保留在 View State 中,但之后发生的任何
更改都应该被跟踪。

不幸的是,没有简单的解决方案。正如我之前解释的,最好的位置是在 Label 本身的 OnInit 之前调用。如果我们在那里设置值,那么该值将不会被
在 Label 本身的 OnInit 之前调用。如果我们在那里设置值,那么该值将不会被
跟踪到隐藏字段中,除非我们在 Label 的 OnInit 完成后进行更改。

所以有两种解决方案

1. 声明性地挂钩到 Init 事件

<asp:Label id="Label2" runat="server" OnInit="lblDate_Init" /> 


这之所以有效,是因为 OnInit 属性在标签自己的 OnInit 事件
发生之前处理,这给了我们一个在它开始跟踪 ViewState 更改之前操作它的机会。
我们的事件处理程序将设置其文本。


2. 创建一个自定义控件

public class DateTimeLabel : Label { 
 
public DateTimeLabel() { 
 
this.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); 
 
} 
}

然后,表单上使用 DateTimeLabel 而不是普通的 Label。由于控件正在
初始化其自己的状态,它可以在跟踪开始之前执行此操作。它尽可能在
构造函数中执行此操作,以便声明的值将被遵守。

这只是 Label 的一个例子。所有控件都会发生这种情况,所以最好将
EnableViewState="false"设置为“false”,除非你有像上面那样的需要跟踪更改但仍不想跟踪初始值的情况。
跟踪初始值。

如果你想使用 Label 或任何控件的回发值,请始终在控件的 OnINit 中初始化,正如我在上面的 DropDownExample 中解释的那样。
或任何控件的回发值,正如我在上面的 DropDownExample 中解释的那样。

5) Label 的替代方案是什么?

Label 不必要地在 HTML 表单中添加 SPAN 标签,并且还设置了视图 状态=true。在你的
ASPX 或 ASCX 文件中执行此操作

<%= Xyz() %> 。这没有向视图 状态.

添加值的问题。如果你编写 <asp:Label ID="jjbb" runat="Server" Text="<%= Xyz() %>" ASP.NET 将会报错。

如果你编写 <asp:Label ID="jbj" runat="Server" Text="<%# Xyz() %>"> 那么这将会把值添加到 View State 中,因为绑定发生在页面生命周期的后期,在 Label 的 OnINit 之后,它只是从 ASCX 文件中获取默认值。
周期之后,它只是从 ASCX 文件中获取默认值。

6) 动态控件呢?

这是我从网上(David Creed 的文章)获得的关于动态控件的信息。

这和之前的问题一样,但由于你对情况有更多的控制权,所以解决起来要容易得多。假设 Joe 编写了一个自定义控件,它在某个时刻
动态地创建了一个 Label。

public class JoesCustomControl : Control { 
protected override void CreateChildControls() { 
Label l = new Label(); 
this.Controls.Add(l); 
l.Text = "Joe's label!"; 
 
} 


}

嗯。动态创建的控件何时开始跟踪视图 状态?你可以在页面生命周期中的几乎任何时间创建
并向你的控件集合添加动态创建的控件,但 ASP.NET 使用 OnInit 阶段来启动
跟踪。视图 状态跟踪。
我们的动态标签不会错过那个事件吗?不。诀窍是,Controls.Add() 不仅仅是
一个简单的集合添加请求。它做了更多。一旦动态控件被
添加到页面中扎根的控件(如果你最终追溯到页面)的控件集合中,ASP.NET 就会在
该控件及其包含的任何控件中执行事件序列的“追赶”。所以,假设你在 OnPreRender 事件中动态添加了一个控件(尽管有很多原因你不想这样做)。
动态地在 OnPreRender 事件中(尽管有很多原因你不希望这样做)。
不想那样做)。那时,你的 OnInit、LoadViewState、LoadPostBackData 和
OnLoad 事件已经发生。控件进入你的控件集合的那一刻,所有
这些事件都会在控件内部发生。这意味着我的朋友,动态控件在你添加它之后立即
立即。除了你的构造函数之外,你最早
跟踪视图 状态可以添加动态控件的时间是在 OnInit 中,子控件已经开始跟踪
ViewState。在 Joe 的控件中,他将它们添加到 CreateChildControls() 方法中,
ASP.NET 在需要确保子控件存在时调用此方法(调用时间可能
因你是否是 INamingContainer、是否是回发以及
是否有其他调用 EnsureChildControl() 的情况而异)。这可能发生的最晚时间是
OnPreRender,但如果它在 OnInit 之后或期间发生,你将再次弄脏
ViewState,Joe。解决方案很简单,但很容易被忽略
微妙。Joe 没有在将标签添加到控件集合后初始化标签的文本,而是

"MARGIN-BOTTOM: 12pt">public class JoesCustomControl : Control { protected override void CreateChildControls() { Label l = new Label(); l.Text = "Joe's label!"; this.Controls.Add(l); } }

在添加之前初始化它。这确保了标签在初始化时绝对不会跟踪
ViewState。实际上,你可以使用这个技巧来做更多的事情,而不仅仅是
初始化简单的属性。你甚至可以在控件成为控件树的一部分之前就绑定控件。
记住我们美国州下拉列表的例子?如果我们可以动态创建该
下拉列表,我们甚至无需禁用其
就可以解决这个问题。它的工作效果非常好。下拉列表的表现就好像这些州只是内置的
视图状态(ViewState)

public class JoesCustomControl : Control { 
 
protected override void OnInit(EventArgs args) { 
 
DropDownList states = new DropDownList(); 
 states.DataSource = this.GetUSStatesFromDatabase(); 
 states.DataBind(); 
this.Controls.Add(states); 
 
} 
} 

列表项一样。它们没有持久化在 ViewState 中,但 ViewState 在控件上仍然启用,这意味着你仍然可以利用其依赖 ViewState 的功能,例如
在控件上,这意味着你仍然可以利用其依赖 ViewState 的功能,例如
OnSelectedIndexChanged 事件。你甚至可以对 DataGrid 执行此操作,尽管这
取决于你如何使用它(如果你使用排序、
分页或使用 SelectedIndex 功能,你将会遇到问题)。
分页或使用 SelectedIndex 功能)。

谢谢

Verinder

© . All rights reserved.