webabcd - 专注于asp.net, html5, silverlight

ASP.NET
从现在开始 一切都不晚
posts - 287, comments - 7866, trackbacks - 594, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
原文地址:http://www.dotnetbips.com/articles/22d33d11-1a75-42c8-bbf6-ca1a345d3fcf.aspx
[原文源码下载]


[翻译]asp.net 2.0中通过压缩ViewState改善性能


原文发布日期:2007.03.08
作者:Bipin Joshi
翻译:webabcd


介绍
开发人员经常担心他们web站点的性能。每一个开发者都想他们的web站点的性能是最优化的。影响你web站点性能的有很多因素,ViewState就是其中之一。本文我将给大家提供一个通过压缩ViewState来改善性能的方法。


什么是ViewState
虽然本文并不是专门来研究ViewState的,但我们还是简单的讨论一下吧。你如果看过web form生成的HTML代码的话,就会发现在一个名为__VIEWSTATE的隐藏域。聪明的ASP.NET会持久化这些控件的值到这个隐藏域中,这对于往返服务器的过程中保存控件的值是非常有用的。但是,此时ViewState会带来性能问题。因为ViewState要在服务端与客户端之间传输,所以会增加网络带宽的流量。

要减少ViewState的话,可以关闭ViewState,就是设置控件的EnableViewState属性,如果设置该属性为false则控件的ViewState将被关闭。但是,如果没有ViewState的话,控件状态持久化的工作就需要你自己来做了,这将是令人非常头痛的。还有另一种减少ViewState的方法就是本文将要介绍的方法,即在ViewState传输之前先压缩它,这样ViewState的数据将会大幅度减少。

在ASP.NET 1.x中如果我们要使用压缩功能的话需要自己写一些代码。而现在.NET 2.0提供了System.IO.Compression命名空间,将会使压缩功能的实现变得非常简单。System.IO.Compression命名空间的GZipStream类可以处理流的压缩和解压,注意,GZipStream只能以流的方式工作。GZipStream类不知专门压缩ViewState而设计的,所以我们为了实现ViewState的压缩和解压要自己写一些代码。


开发ViewStateHelper
我们首先用Visual Studio新建一个web站点,然后再App_Code文件夹中增加一个名为ViewStateHelper的类。类文件顶部要引入的命名空间如下
using System.IO;
using System.IO.Compression;

System.IO命名空间为我们提供了流的类,如MemoryStream。The System.IO.Compression命名空间为我们提供GZipStream类,它允许你呈现gzip格式的数据(RFC 1952)。


压缩数据
现在增加一个名为Compress()的方法如下
public static byte[] Compress(byte[] data)
{
    MemoryStream ms 
= new MemoryStream();
    GZipStream stream 
= new GZipStream(ms, CompressionMode.Compress);
    stream.Write(data, 
0, data.Length);
    stream.Close();
    
return ms.ToArray();
}

这个Compress()静态方法接收一个字节数组类型的变量并压缩它,返回的是一个压缩后的字节数组。它首先实例话一个MemoryStream类,它用来呈现内存中的流。然后通过MemoryStream和压缩模式两个参数创建一个GZipStream对象。你可以用任何流类型来替换MemoryStream。被压缩的数据将写进这个流。GZipStream类的Write()方法会接收一个字节数组类型的变量,然后压缩它并写进流中(在我们的例子中是MEmoryStream)。写完之后GZipStream被关闭。最后,MemoryStream的ToArray()方法将转换流数据到字节数组中。


解压数据
为了解压数据,我们要增加另一个名为Decompress()的方法,其关键代码如下
public static byte[] Decompress(byte[] data)
{
    MemoryStream ms 
= new MemoryStream();
    ms.Write(data, 
0, data.Length);
    ms.Position 
= 0;
    GZipStream stream 
= new GZipStream(ms, CompressionMode.Decompress);
    MemoryStream temp 
= new MemoryStream();
    
byte[] buffer = new byte[1024];
    
while (true)
    
{
        
int read = stream.Read(buffer, 0, buffer.Length);
        
if (read <= 0)
        
{
            
break;
        }

        
else
        
{
            temp.Write(buffer, 
0, buffer.Length);
        }

    }

    stream.Close();
    
return temp.ToArray();
}

这个Decompress()静态方法接收一个已压缩的字节数组,返回一个解压后的字节数组。首先,它创建了一个MemoryStream对象,并将一个已压缩的字节数组写进去。然后这个流将作为参数提供给GZipStream的构造函数,注意此时的压缩模式为Decompress。解压后的数据也需要在某个地方保存,所以接下来创建的另一个MemoryStream对象就是作此用途。接着我们用一个while循环从GZipStream读取出解压后的数据,每一块都是1024字节。然后这个数据就被写进MemoryStream了。最后,GZipStream被关闭,解压后的内容作为一个字节数组被返回。


新建一个web form
现在打开一个web form,拖拽一个SqlDataSource控件到上面,配置它以使其可以从Northwind数据库中读取Customers表的记录。


拖拽一个GridView控件并设置它的DataSourceID属性为你刚才配置好的SqlDataSource控件的ID。确保运行这个web form是我们想要的结果,示例如下



定制ViewState的序列化和反序列化
你不用关心ViewState的序列化和反序列化,因为这些都是自动的。但是因为咱们需要压缩ViewState,所以需要定制它的序列化和反序列化。Page基类有两个虚拟方法,SavePageStateToPersistenceMedium()和LoadPageStateFromPersistenceMedium()。它们分别允许你定制ViewState的序列化和反序列化。

我们首先在web form的后置代码中重写SavePageStateToPersistenceMedium()方法。在这个方法中,你将压缩ViewState并存储它到隐藏域中。代码如下
protected override void SavePageStateToPersistenceMedium(object state)
{
    LosFormatter formatter 
= new LosFormatter();
    StringWriter writer 
= new StringWriter();
    formatter.Serialize(writer, state);
    
string viewState = writer.ToString();
    
byte[] data = Convert.FromBase64String(viewState);
    
byte[] compressedData = ViewStateHelper.Compress(data);
    
string str = Convert.ToBase64String(compressedData);
    ClientScript.RegisterHiddenField(
"__MYVIEWSTATE", str);
}

SavePageStateToPersistenceMedium()方法接收一个ViewState对象。在该方法中创建一个LosFormatter对象,LosFormatter类允许你序列化和反序列化ViewState中的数据。LosFormatter类的Serialize()方法接收两个参数,分别是一个流和一个对象,结果是把这个对象序列化到这个流中。在我们的例子里,ViewState被序列化到了StringWriter里。StringWriter的ToString()方法返回一个字符串用于呈现ViewState数据。这个字符串是Base64格式的(在ASP.NET中就是这么存储到ViewState中的),我们需要把它转换成我们想要的格式。Convert类的FromBase64String()方法就可以做这个工作。FromBase64String()方法返回一个字节数组数据,然后使用我们的ViewStateHelper类的Compress()方法压缩它。然后使用Convert类的ToBase64String()方法把被压缩的数据转换成Base64格式。最后,我们用ClientScript的RegisterHiddenField()方法把这个已压缩的Base64格式的数据注册到一个名为__MYVIEWSTATE的隐藏域中。

这就是我们压缩ViewState的方法。另外还有一个同等重要的工作就是解压,我们通过重写LoadPageStateFromPersistenceMedium()方法来实现这样的功能,其代码如下
protected override object LoadPageStateFromPersistenceMedium()
{
    
string viewstate = Request.Form["__MYVIEWSTATE"];
    
byte[] data = Convert.FromBase64String(viewstate);
    
byte[] uncompressedData = 
    ViewStateHelper.Decompress(data);
    
string str = Convert.ToBase64String(uncompressedData);
    LosFormatter formatter 
= new LosFormatter();
    
return formatter.Deserialize(str);
}

这个方法首先读出__MYVIEWSTATE隐藏域的值,该值为Base64格式的被压缩ViewState。为了解压它,你的第一步工作仍然是把它转换为一个字节数组,我们通过Convert类的FromBase64String()方法来做。接下来我们用ViewStateHelper类的Decompress()方法解压数据。然后用Convert类的ToBase64String()方法再一次把解压后的数据转换为Base64格式的字符串。最后,LosFormatter对象反序列化解压后的ViewState

以上就是压缩和解压ViewState的全部内容。经过我对本例的实际测试发现未经压缩的ViewState大小为13,568字节,压缩后的ViewState大小为5,932字节。这对于改善性能是非常有用的,你说呢?


总结
从本文中你学到了如何通过压缩ViewState来改善你web站点的性能。GZipStream类提供了现成的把你的数据压缩成gzip格式的方法。被压缩的数据需要通过重写Page基类的SavePageStateToPersistenceMedium()方法保存。相似的,读取ViewState后我们需要通过重写LoadPageStateFromPersistenceMedium()来解压数据。


作者:Bipin Joshi
Email:http://www.dotnetbips.com/contact.aspx
简介:Bipin Joshi是DotNetBips.com的管理员。他是http://www.binaryintellect.com/的发起人,这个公司提供.NET framwork的培训和咨询服务。他在印度孟买为开发者提供培训。他也是微软的MVP(ASP.Net)和ASPInsiders的会员。

Feedback

#1楼  回复 引用   

2007-03-13 10:52 by lonfone[未注册用户]
不知道由此减小VIEWSTATE大小改善的性能 与 为压缩VIEWSTATE而增加的 压缩/解压操作 而多耗费的性能 互相抵消后, 有多大收益呢?

说到底是 服务器/内存/CPU 运算能力 紧张, 还是带宽紧张 的比较.

不知道压缩VIEWSTATE是否真的对系统运行有好处, 或 获得的好处是否大于增加的坏处.

#2楼[楼主]  回复 引用 查看   

2007-03-13 12:00 by webabcd      
@lonfone
嗯,同意
主要看系统的瓶颈是带宽还是服务器运算能力了

#3楼  回复 引用   

2007-03-13 12:57 by ivw[未注册用户]
呵呵,不知道有没有真正测试过提升了多少的性能。到底是不是值得去这样做,还是说压缩只是一个美丽的花瓶

#4楼[楼主]  回复 引用 查看   

2007-03-13 17:26 by webabcd      
@ivw
测试结果viewstate能压缩一倍以上
对于带宽紧张,而cpu富裕的服务器可以考虑
另外如果不在乎cpu,而是关心你的用户与服务器交互有一个良好的速度的话也可以考虑

#5楼  回复 引用   

2007-05-10 12:33 by d[未注册用户]
d

#6楼[楼主]  回复 引用 查看   

2007-05-10 19:55 by webabcd      
@d
嗯,一个字母,简单明了

#7楼  回复 引用   

2008-07-31 21:34 by 沉紫龙[未注册用户]
看了后,我反而会觉得增加服务器的性能,带宽不是靠减小VIEWSTATE来带来的,主要还是css+DIV的架构

#8楼[楼主]  回复 引用 查看   

2008-08-01 07:59 by webabcd      
@沉紫龙
是的,而且gzip压缩可以由iis去做
而且用mvc的话也就没有VIEWSTATE的烦恼了

就当这篇文章是介绍如何压缩和解压缩数据吧

#9楼  回复 引用   

2008-08-20 16:25 by cncqc[未注册用户]
不建议这样做。本人曾在本地做过测试,datBegin在Page_Init中赋值,将时耗写到Reader中,查看输出1000条记录的时耗,有压缩过的比不压缩的,时耗多出30ms,不信的,大家可以做个测试。此文章算是作为技术研讨吧。本人比较喜欢Blog作者的东西,希望以后多出好文章!

#10楼[楼主]  回复 引用 查看   

2008-08-21 09:14 by webabcd      
@cncqc
:)
确实是这样的

#11楼  回复 引用   

2008-11-17 17:50 by 理想天空[未注册用户]
你好我按上面说的做了,其它的都不会有问题,可是我的gridview里取不到行数了,都只有0行,是不是哪里有问题?我是加在basepage里的

#12楼[楼主]  回复 引用 查看   

2008-11-17 18:50 by webabcd      
@理想天空
绑定之后再取行数

#13楼  回复 引用   

2008-12-11 08:57 by skyland84[未注册用户]
是很不错啊~~~

减少一半之多。很乐观

#14楼[楼主]  回复 引用 查看   

2008-12-11 12:04 by webabcd      
@skyland84
嗯,就是会加重服务器负担
而且gzip压缩可以由iis去做

#15楼  回复 引用   

2009-07-06 09:12 by 未知[未注册用户]
.。。。。。。。。。。。。。。。。。。。。

#16楼[楼主]  回复 引用 查看   

2009-07-06 12:06 by webabcd      
@未知
。。。
啥意思?

#17楼  回复 引用 查看   

2009-08-22 11:53 by klvk      
是不是每次回传的时候GridView的PageIndex都是零啊?

不论我怎么分页 每次翻页只能在第二页不变

#18楼[楼主]  回复 引用 查看   

2009-08-25 08:37 by webabcd      
@klvk
当然不是喽,不知道你是怎么分页的,但是PageIndex不会总是零的

#19楼  回复 引用 查看   

2009-08-25 13:16 by klvk      
哦,我们不用viewstate了 需要缓存的数据量太大了,页面加载时间很长。
这个问题原因再找找看,可能是其他地方出问题了

#20楼[楼主]  回复 引用 查看   

2009-08-25 16:52 by webabcd      
@klvk
:)
好的

#21楼  回复 引用 查看   

2009-10-27 11:05 by 王金平      
引用沉紫龙:看了后,我反而会觉得增加服务器的性能,带宽不是靠减小VIEWSTATE来带来的,主要还是css+DIV的架构

#22楼[楼主]  回复 引用 查看   

2009-10-27 12:19 by webabcd      
@王金平
:)
多谢支持

#23楼  回复 引用 查看   

2010-11-24 09:09 by 我要代码      
看了这篇文章,对我的项目很有用,我正为重达107K的VIEWSTATE烦恼呢!!呵呵,感谢楼主

#24楼[楼主]  回复 引用 查看   

2010-11-24 12:07 by webabcd      
@我要代码
:)
翻译的
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 672587 UWw9AIMIncI=