一、
DataGrid
控件中的
ViewState
在
Asp.Net
中,我们使用最多的恐怕就是
DataGrid
列表控件了。这个控件的功能的却非常强大,也非常好用。它不但可以实现任意的格式化选择,而且还可以动态进行分页、排序、添加按钮、动态编辑等功能。可以说,
DataGrid
控件已经实现了我们需要的大部分功能。
遗憾的是,上面说的很多非常实用的功能,大部分需要
ViewState
的支持,就是说
DataGrid
控件需要通过
ViewState
来保存控件的状态的,如果我们关闭了
ViewState
,即在
HTML
代码中使用了
EnableViewState
="False"
属性,那么,上面的所有有用的功能我们将无法使用。
而
ViewState
的缺点也是很大的,就是
DataGrid
会把所有的数据源中的数据存放到
ViewState
中。
DataGrid
是一个列表控件,它存放在
ViewState
中的数据包括了列表中所有单元格(
Cell
)中的数据,更加可恨的是,即时我们打开了分页功能,它也要把所有的没有显示出的数据源中的数据放到
ViewState
里面,如果不在数据库端控制数据量大小的话,
DataGrid
控件实际上是把数据库表中所有数据统统都存放在
ViewState
中。直接后果就是
Asp.Net
生成发送到客户端的源
Html
代码异常庞大。
大家知道,
Asp.Net
中,
ViewState
实际上是一个名称
/
值对的字典,为了正确在各种编码的网页中保存,将
ViewState
序列化后又进行了
Base64
编码。这将更加加大
ViewState
的数据量。
我试验了一下,一个包括
5000
条记录的数据库,如果不在数据库端控制数据量的话,直接使用
Select * From TableName
这样的
Sql
语句绑定数据的话,一个
Asp.Net
动态网页(仅有一个
DataGrid
控件的测试页)的
ViewState
有
60k
之多。而这样一个网页放到公共
Web
网站,缓慢的网页下载速度将会使大部分人望而却步,使用
56k
猫上网的人就更不要提了,根本无法访问这样的网页,等待的时间将不可忍受。
所以大部分有
Asp.Net
开发经验的开发者,如果不使用
DataGrid
的高级功能的话,一般是把
EnableViewState
="False"
属性给加入的,还有就是使用自己的分页控件,不使用
DataGrid
提供的分页功能,这将取消
DataGrid
的
ViewState
,大大减少最终生成的
Html
的代码量。
二、
DataGrid
中必须使用
ViewState
的一些功能
不错,很多情况下我们仅需要
DataGrid
的显示列表数据的功能,或者使用自己的分页功能,这种情况下可以使用
EnableViewState
="False"
属性,但是如果我们需要使用
DataGrid
的动态编辑、按钮列,或者要访问
DataGrid
的索引项时,我们必须把
DataGrid
的
ViewState
打开,这将不可避免的出现
ViewState
数据量过大的现象,但是我们没有办法。
我大略统计了一下
DataGrid
中需要使用
ViewState
的一些功能属性,它们都必须使用
ViewState
,而这些属性我们很多情况下都必须使用:
DataGrid.CurrentPageIndex DataGrid
分页的页索引
DataGrid.DataKeys
取每个记录的键值
DataGrid.EditItemIndex
编辑项的索引
DataGrid.Items DataGrid
中行的集合
DataGrid.PageCount DataGrid
分页的总页数
而在使用
DataGrid
内置事件时,传递事件参数的
DataGridItemEventArgs
、
DataGridCommandEventArgs
、
DataGridSortCommandEventArgs
、
DataGridPageChangedEventArgs
类更加要使用
DataGrid
的
ViewState
,否则它们将无法工作。
这里我给一个简单的示例,在
DataGrid
中加入一个
CheckBox
列,这使当页面回发的时候我可以获取
DataGrid
的键值。但是如果关闭
ViewState
的话,此功能无法使用。
Html
:
<
Asp:DataGrid
ID=”myDataGrid”Runat=”Server”DataKeyField=”ID” AutoGenerateColumns=”False”>
<
Columns
>
<
asp:TemplateColumn
>
<
ItemTemplate
>
<
Asp:CheckBox
ID=”myBox”Runat=”Server”/>
</
ItemTemplate
>
</
asp:TemplateColumn
>
</
Columns
>
</
Asp:DataGrid
>
<
br
/><
br
/>
<
Asp:Button
ID=
”bntSubmit”
Runat=
”Server”
Text=
”OK”
/>
Code
:
private
void btnSubmit_Click(object sender, System.EventArgs e)
{
CheckBox cb;
string str;
for(int i=0;i<myDataGrid.tems.Count;i++)
{
cb = (CheckBox)myDataGrid.Items[i].Cells[2].FindControl("myBox");
if(cb.Checked)
str += myDataGrid.DataKeys[myDataGrid.Items[i].ItemIndex].ToString();
}
//
此时
str
变量就是
DataGrid
中选中行的数据库键值。
}
上面的代码使用很成功,不过如果在
DataGrid
中使用了
EnableViewState=”False”
属性,那么上面的代码根本无法起作用。
三、去掉
ViewState
中
DataGrid
的无用数据
终于说到正题了,我这篇文章说的就是怎样去掉
ViewState
中的无用数据,而保留有用的数据。文章写到这里,我们可以很明显的看出来,
DataGrid
保存在
ViewState
中的数据分为两个部分,一部分是保存索引用的,就是
DataKeys
和
DataItems
这样的属性使用的数据,我们把它称之为索引数据。还有一部分是
DataGrid
中数据源的内容,我们称之为列表数据。
我们如果把实际上无用的列表数据从
ViewState
中去除,这样可以大大减小页面
ViewState
的数据大小,使用
DataGrid
时
ViewState
数据量太大的根本原因就是列表数据存放在
ViewState
中。
我在微软的
.Net Framework SDK
文档中没有找到关闭列表数据
ViewState
的任何内容,就是说微软没有给出
DataGrid
运行时的任何调用顺序和内部的工作机制。没办法,我使用了一个工具来获得
DataGrid
的内部工作流程,发现它在数据绑定初始化的时候,生成了一个叫
DataGridTable
的控件对象,这个对象是继承
System.Web.UI.WebControls.Table
控件的。而且这个对象是最先加入(使用
Controls.Add()
方法)
DataGrid
中的。而且
ViewState
中的
DataGrid
列表数据也是这个控件加入到
DataGrid
中的。实际上,
ViewState
中的
DataGrid
的单元格中的数据实际上是
System.Web.UI.WebControls.Table
控件的
SaveViewState()
方法给加进去的。这些数据很多情况下是不需要的。
好,原因找到了,解决问题就好办了,因为微软已经给出了控制
DataGrid
中子控件的方法。我们既然知道
DataGridTable
控件是
DataGrid
中最先生成的控件,那么我们通过
DataGrid.Controls
属性就可以直接获取
DataGrid
中子控件的引用,获得引用后就可以控制子控件了。解决方法就是在数据绑定的时候,设置
DataGrid
中
DataGridTable
控件的
EnableViewState
属性为
False
就可以了。
代码如下:
首先必须重写
DataGrid.ItemDataBound
或
DataGrid.ItemCreated
事件,我们用它们来控制在向客户端写
html
之前
DataGrid
的动作。这两个事件任选其一,都可以实现效果。我们使用
ItemDataBound
事件来写例子,
DataGrid
示例
ID
为
myDataGrid
。
[C#]
首先在页面初始化中的
InitializeComponent()
方法内加入事件的委托:
private
void InitializeComponent()
{
this.myDataGrid.ItemDataBound +=
new
DataGridItemEventHandler(this.myDataGrid_ItemDataBound);
}
然后在
myDataGrid_ItemDataBound
方法内加入控制代码:
private
void myDataGrid_ItemDataBound(object sender, DataGridItemEventArgs e)
{
myDataGrid.Controls[0].EnableViewState = false;
}
[VB.Net]
Private
Sub myDataGrid_ItemDataBound(sender As Object,e As DataGridItemEventArgs) _
Handles
myDataGrid.ItemDataBound
myDataGrid.Contols.Item(0).EnableViewState = False
End Sub
好了,使用
DataGrid
时,把上面的代码加入,将减小使用
DataGrid
时
ViewState
的
90%
的数据量。而且,
DataGrid
中许多使用
ViewState
的功能丝毫不少,岂不是两全其美?
注意:
1.
上面说的法子有一种情况下不能使用,就是使用
DataGrid
的内部分页功能时,重写
DataGrid.PageIndexChanged
事件时,调用
DataGridPageChangedEventArgs
时,必须把所有的
ViewState
打开,包括列表数据,关闭任何的
ViewState
,都将导致
DataGridPageChangedEventArgs
的索引丢失,无法分页。
不过这个缺点很好解决,很多人已经写了自定义的分页控件,这些控件是不需要
DataGrid
的任何数据的。可以提供非常完美的分页功能。
2.
(重要)我春节的这几天对上面说的办法进行了详细的测试,发现上面的办法确实有效,可以大大的减少
aspx
页面的
ViewState
的数据量,但是这个办法在使用上有一个注意的地方,否则会出现数据无法显示的毛病。
我一直在奇怪,微软写
DataGrid
为什么要加入一个
DataGridTable
类,而且还要把所有的数据库数据存放在
ViewState
中。经过测试我才发现,这是因为有了
System.Web.UI.Page.IsPostBack
属性的缘故。我们经常喜欢利用
Page.IsPostBack
属性检测页面是否是第一次运行,如下代码:
private
void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{
//
页面第一次运行
,
执行数据绑定
SqlConnection conn = new SqlConnection(“...”);
SqlCommand comm = new SqlCommand(“...”,conn);
...
...
myDataGrid.DataBind() //
数据绑定
}
}
当然,网页执行第一次时,
DataGrid
的内容正常显示,而使用了上面的去除
ViewState
方法后,页面如果回发处理,
DataGrid
的内容将会消失。我才明白
DataGrid
内
DataGridTable
把数据存放在
ViewState
内的用意。微软的设计是非常严谨的,他们的用意就是当使用
Page.IsPostBack
属性时,仅访问一次数据库就可以永久保持
DataGrid
的数据(在不离开此页面的情况下),数据存放的地点就是页面的
ViewState
中。这样页面回发后,
DataGrid
就可以从
ViewState
中重新生成
DataGrid
的显示内容,无需访问数据库。所以说微软以牺牲客户下载的速度(
ViewState
数据量)来保证服务器的资源,大家都知道频繁访问数据库对服务器的资源消耗很大。
所以,使用上面减少
DataGrid
的
ViewState
数据的法子是可行的,但是必须使所有的页面回发处理都必须进行数据绑定,否则
DataGrid
无法获得数据库内容,也无法获得
ViewState
中保存的数据,那么回发后
DataGrid
将无法显示任何内容。
总结,使用上面减少
ViewState
的办法可以大大加快客户端的下载显示速度,但是频繁的数据库访问将加大服务器的压力;使用
ViewState
可以减轻服务器的压力,但是又加大了客户端的下载时间,它们是互相矛盾的。所以开发者要根据实际情况选择是否使用
DataGrid.Controls[0].EnableViewState=false;
的法子,如何选择,大家请自己斟酌。