自学Web开发第十五天-基于VB和ASP.NET;继续深入研究GridView,扩展到ViewState,刷新GridView的功能实现。
在使用GridView时,各种事件都需要重新绑定,而绑定时候必须重新给
GridView.DataSource
属性赋值,即重新设置数据源。在我们不使用数据源控件时,每次都需要查询数据库,或者设置数据集,十分占资源且麻烦。有没有直接使用现有数据源的方式呢?
失败方案1:从GridView控件的DataSource属性着手
开始我想能不能从控件的DataSource属性重新获取数据集,经过测试后发现,一旦绑定数据,则DataSource属性就空了。
失败方案2:使用静态类存储数据集
静态类可以存储需要的数据集,且不主动释放就能够一直存在。不过经过测试,静态类和静态数据类似Application
,是全应用共享的,多用户使用同一份数据,造成了混乱。
不失败但是也不算成功的方案3:使用动态类保存数据集
静态类不行只能使用动态类了。每次获取新数据集实例化并绑定时,使用一个全局的变量指向这个数据集,可以符合要求。在不更改数据时,可以使用变量指向的实例进行重新绑定。这样实例一直存在可以一直使用,当数据更新时重新实例化并且变量指向新的就可以。
但是这样做有个问题,就是每多一个需要保存的数据集(可能是控件增加、或一些暂存的查询状态等),都需要增加一个全局变量。在实际应用中不方便。
不失败但是也不满足需求的方案4:使用全局ArrayList动态添加指针
在上一个方案的基础上,将全局变量改成ArrayList,就可以动态的添加指针了。但是使用过程中容易引起混乱,经常搞错哪个指针对应哪个数据集……
已经勉强能使用的方案5:使用Session保存指针
Session可以保存键值对,查找就会方便很多,基本算是已经符合要求了。不过Session的生存周期太长了,如果应用中多个页面都需要进行类似操作,就会造成Session的规模越来越大。而手动释放Session的时机和释放对象却不好把握,所以这个方案算是勉强能使用。
上一个方案的扩展方案6:使用ViewState
初学ASP.net时候,绕不过七大对象,分别是Page、Request、Response、Session、Application、Server、Cookie,而ViewState可以说是ASP.net的第八大对象了。
什么是ViewState
ViewState到底是什么?其实ViewState是用于维护页面的UI状态。Web本身是没有状态的,ASP.NET页面也没有状态,它们在到服务器的每个往返过程中被实例化、执行、呈现和处理。开发人员可以使用一些技术(如以会话状态将状态存储在服务器上,或将页面回传到自身)来添加状态。
例如一个页面进行提交,数据回传到服务器经过处理后,将结果页面发至客户端的过程:
窗体记录了输入的各有效值信息,这是因为HTML窗体元素会在HTTP标头中将其当前值从浏览器发送到服务器,可以使用ASP.NET跟踪来查看回传的窗体值。在ASP.NET之前,开发人员不得不通过多次回传,从HTTP窗体中提取回传值,添加到窗体字段中。现在ASP.NET可以自动完成这项任务。
而ViewState是ASP.NET的一种机制,用来跟踪服务器控件的状态值,否则这些值将不作为HTTP窗体的一部分回传。例如Label控件显示的文本默认情况下就保存在ViewState中。如果没有ViewState,像Label控件这类的值就无法自动回传。在开发中,ViewState可以绑定数据,或者在首次加载页面时对Label编程设置一次,ViewState就记录下其状态。以后该控件的值将自动从ViewState中填充,而不用再进行编程设置。因此,除了可以减少工作和代码,ViewState通常还可以减少数据库的往返次数。
ViewState的工作原理
ViewState是由ASP.NET页面框架管理的一个隐藏的窗体字段。当ASP.NET执行某个页面时,该页面上的ViewState值和所有控件将被收集并格式化成一个编码字符串,然后被分配给隐藏窗体字段(即<input type=hidden>)__VIEWSTATE的值属性,由于隐藏窗体字段是发送到客户端的页面的一部分,所以ViewState值被临时存储在客户端的浏览器中。如果客户端将该页面传回服务器,则ViewState字符串也将被回传。回传后,ASP.NET页面框架将解析ViewState字符串,填充到该页面和各控件。
使用ViewState需要注意的问题
- 如果要使用 ViewState,则在 ASPX 页面中必须有一个服务器端窗体标记 (<form
runat=server>)。窗体字段是必需的,这样包含 ViewState信息的隐藏字段才能回传给服务器。而且,该窗体还必须是服务器端的窗体,这样在服务器上执行该页面时,ASP.NET页面框架才能添加隐藏的字段。 - 页面本身将 20 字节左右的信息保存在 ViewState 中,用于在回传时将 PostBack 数据和ViewState 值分发给正确的控件。因此,即使该页面或应用程序禁用了 ViewState,仍可以在 ViewState中看到少量的剩余字节。
- 在页面不回传的情况下,可以通过省略服务器端的 标记来去除页面中的 ViewState。
如何使用ViewState
ViewState 为跨回传跟踪控件的状态提供了一条神奇的途径,因为它不使用服务器资源、不会超时,并且适用于任何浏览器。ViewState使用的编程语法于Session和Cache语法类似,均是以键值对形式存储。因为ViewState存储信息是格式化成编码字符串,所以调用时需指定数据的类型。
ViewState("name") = "Hanmeimei" '保存在ViewState中
Dim name As String = CStr(ViewState("name"))
ViewState存储的数据类型
ViewState不能存储所有的数据类型,仅支持:String、Integer、Boolean、Array、ArrayList、Hashtable等能被序列化的数据。自定义类型需添加Serializable使之序列化才能被ViewState存储。
选择Session还是ViewState?
保存状态我们可以使用Application、Session、Cache、Cookie、ViewState,那我们选哪个好呢?需要保存的状态或数据从层级分共有3个层级:应用级、用户级、页面级,从存储空间分有3部分:客户端、浏览器、服务器。
Application:应用级,保存在服务器。
Session:用户级,保存在服务器。
Cache:应用级,保存在服务器。
Cookie:用户级,保存在客户端。
ViewState:页面级,保存在浏览器。
这里,我们只研究ViewState的优缺点。
- 因为ViewState是存储在页面中,每次回传时都进行传输,无形中增加了回传窗体的大小。如果存储了大量的数据,则会大大的增加HTML的有效负载,而不论这些数据在什么时候能够用的上。
- 尽管ViewState已经被编码,并且可以选择对其加密,但始终是不将数据发送到客户端才是最安全的。如果已经确认了链接的安全性除外。
- ViewState序列化程序只为一小部分常用的对象类型进行了优化,其他可序列化的类型或许可以保留在 ViewState中,但速度会变慢,并会生成一个非常大的 ViewState。
但是和Session比起来, ViewState又具有以下优点:
- 因为 ViewState是将数据存储在浏览器页面中,所以不占用服务器资源。在高速局域网十分普及而服务器资源对中小企业来说比较珍贵的业务场景,比Session更合适。
- 只要浏览器页面不关闭, ViewState的数据就不会丢失。
- ViewState是页面级的,离开页面后数据就释放,更加灵活,而Session资源的释放时机不好把握。
使用ViewState获得更好的性能
使用ViewState时,每个对象都必须先序列化到 ViewState 中,然后再通过回传进行反序列化,因此使用 ViewState 并非是没有代价的。为了减少性能影响,可以做到以下几点:
- 减少使用ViewState。默认情况下ViewState将被启用,但是很多时候对应用并没有什么用处。因此如果不需要使用ViewState(如并不需要通过后台编程代码设置的Label控件等),最好还是将它关闭。可以基于每个控件、每个页面来关闭ViewState(相应控件、窗体或页面中添加EnableViewState=“false”)。例如页面不回传自身、处理的不是控件的事件、控件没有动态的或数据绑定的属性值(或对于每一个请求它们都设置在代码中)。
- 使用优化过的 ViewState 序列化程序。一些基本类型具有专门的序列化程序,这些程序运行速度很快,并已经过优化,可以生成很小的 ViewState。如果要序列化一个其他的类型,可以创建一个自定义 TypeConverter 来显著提高它的性能。
- 尽量减少使用对象,如果可能,尽量减少放入 ViewState 中的对象的数目。例如,不要使用二维字符串数组(名称/值,其对象的数目与数组的长度一样多),而应使用两个字符串数组(只有两个对象)。但是,在将两个已知类型存储在 ViewState 中之前,在这两者之间转换不会获得任何性能提高,因为这样做实际上相当于付出了两次转换的代价。
增加ViewState的安全性
由于 ViewState 没有被格式化为清晰的文本,某些人有时会认为它被加密了,其实并没有。相反,ViewState 只是进行了 Base64 编码,以确保值在往返过程中不会发生变化,而并不考虑应用程序使用的响应/请求编码。
在使用中,可以向应用程序中添加两种安全级别:防篡改和加密。需要注意的是,ViewState 安全性对于处理和呈现 ASP.NET 页面所需的时间有直接的影响。简单地说,安全性越高,速度越慢。因此如果不需要,不要为 ViewState 添加安全性。
- 尽管赛列代码不能确保ViewState字段中实际数据的安全,但它能够显著降低有人通过ViewState骗过应用程序的可能性,即防止回传应用程序通常会禁止用户输入的值。
<%-- 通过设置EnableViewStateMAC属性让ASP.NET向ViewState追加散列代码 --%>
<%@Page EnableViewStateMAC = "True" %>
在回传时,ASP.NET将为ViewState数据生成一个散列代码,并将其于存储在回传值中的散列代码进行比较。如两个散列代码不匹配,则丢弃该ViewState数据,同时恢复控件。默认情况下,ASP.NET使用SHA1算法来生成散列代码,也可以通过在machine.config
文件中设置<machineKey>来选择MD5算法。
<machineKey validation = "MD5" />
- 可以使用加密来保护ViewState的实际数据。使用时同添加散列代码,不同的是算法类型使用3DES。
<machineKey validation = "3DES" />
使用ViewState实现刷新而不是处处更新GridView
我们可以将数据库里查询到的DataSet暂存到ViewState里,在不需要更新的时候调用,即刷新绑定。而需要更新操作的时候,访问数据库,并更新暂存数据就行了。
再次强调,ViewState是将数据存储在页面文件了。如果数据表很大,则ViewState会十分庞大,页面加载传输速度也会显著的受到影响!
''' <summary>
''' 从数据源更新GridView的数据,可以使用Sql查询命令、DataSet、DataTable、DataRow()
''' </summary>
''' <param name="GridView">需更新的GridView控件</param>
''' <param name="DataSource">
''' 从数据库更新数据时,所使用的数据库查询命令<br/>
''' 从数据表更新数据时,所使用的DataSet/DataTable/DataRow()
''' </param>
''' <returns>是否更新成功</returns>
''' <remarks>
''' 注册使用ViewState("GridViewDataset." + GridView.ID)和ViewState("GridViewSQL." + GridView.ID)
''' </remarks>
''' <example>
''' 返回是否从数据源重新更新GridView的数据信息
''' <code>
''' Dim result As Boolean = UpdateBind(GridView1,"select * from 用户信息表")
''' </code>
''' 如果返回False,则是更新失败
''' </example>
Private Function UpdateBind(ByRef GridView As GridView, ByRef DataSource As Object) As Boolean
'判断参数
Dim ds As DataSet
If DataSource.GetType = GetType(String) Then '参数2是字符串型,也就是SQL命令
ViewState("GridViewSQL." & GridView.ID) = DataSource '暂存下查询数据库的SQL命令
ds = GetDataSet(DataSource) '查询数据库函数,根据数据库命令返回查询到的DataSet,如果查询到到数据行为0,则返回空
If ds Is Nothing Then Return False
ElseIf DataSource.GetType = GetType(DataSet) Then '参数2是Dataset,直接使用
ds = DataSource
ElseIf DataSource.GetType = GetType(DataTable) Then '参数2是DataTable,获取其DataSet。
ds = CType(DataSource, DataTable).DataSet
ElseIf DataSource.GetType Is GetType(DataRow()) Then '参数2是一个DataRow的数组集合,套入到一个DataSet
Dim DataSet As New DataSet
DataSet.Tables.Add(CType(DataSource, DataRow())(0).Table.Clone) '新建一个表,结构为DataRows()所在表的克隆
For Each row In CType(DataSource, DataRow())
DataSet.Tables(0).ImportRow(row) '复制每行数据
Next
ds = DataSet
Else
Dim Exception As New ArgumentException("UpdateBind方法的参数不正确:无法识别的参数2")
Throw Exception
End If
GridView.DataSource = ds
ViewState("GridViewDataSet." & GridView.ID) = ds '暂存下GridView所绑定的DataSet
GridView.DataBind()
Return True
End Function
''' <summary>
''' 对于已经使用UpdateBind()方法更新的GridView控件,不访问数据源进行刷新操作。
''' </summary>
''' <param name="GridView">需刷新的GridView控件</param>
''' <returns>是否刷新成功</returns>
''' <remarks>
''' 在GridView内容没有改变,只是需要改变样式时使用。比如GridView的换页、调整分页大小等操作。
''' 需使用到ViewState("GridViewDataset." + GridView.ID)和ViewState("GridViewSQL." + GridView.ID)。
''' 如果没有经过UpdateBind()方法更新,则会返回False或是错误的数据。
''' </remarks>
Private Function RefreshBind(ByRef GridView As GridView) As Boolean
Dim ds As DataSet
If ViewState("GridViewDataSet." & GridView.ID) Is Nothing Then
If ViewState("GridViewSQL." & GridView.ID) Is Nothing Then
Dim Exception As New ArgumentException("RefreshBind错误,找不到ViewState的数据集以及获取数据集的数据库命令")
Throw Exception
Else
ds = GetDataSet(ViewState("GridViewSQL." & GridView.ID)) '没有暂存数据表,但是有暂存的SQL命令,则重新查询数据库,并进行绑定更新
ViewState("GridViewDataSet." & GridView.ID) = ds
End If
Else
ds = ViewState("GridViewDataSet." & GridView.ID) '有暂存的数据表,使用暂存刷新
End If
GridView.DataSource = ds
GridView.DataBind()
Return True
End Function