view state压缩优化提速

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。


Recently, I developed a huge ASP.NET page, with more than 30 controls. As we all know, it's a good idea to disable the ViewState for the controls that don't actually need it, say Literals or Labels. After doing that, I noticed that the hidden ViewState field was still a few KBs big. This is obviously a big problem for the users that still don't have a broadband connection, because uploading 40 KB to the server is really a bad issue, especially when they begin to click the "Submit" button again and again because they don't notice any response. So, after a few searches through the Internet, I built a simple solution to compress the ViewState and therefore save a rough 50% of the bandwidth. This post by Scott Hanselman has been particularly useful. Although it's possible to use external libraries to perform compression tasks, I think the better solution is to use the GZipStream or DeflateStream that the .NET Framework 2.0 includes.

Compressing and Decompressing Data in Memory

First of all, we need a way to compress and decompress an array of bytes in memory. I put together this simple static class that exposes two methods: Compress and Decompress. The two available classes, GZipStream and DeflateStream, according to MSDN, use the same algorithm, so it's irrelevant which one you choose.

The code below is really simple, and doesn't need further explanation:

Collapse Copy Code
using System.IO;
using System.IO.Compression;

public static class Compressor {

  public static byte[] Compress(byte[] data) {
    MemoryStream output = new MemoryStream();
    GZipStream gzip = new GZipStream(output, 
                      CompressionMode.Compress, true);
    gzip.Write(data, 0, data.Length);
    return output.ToArray();

  public static byte[] Decompress(byte[] data) {
    MemoryStream input = new MemoryStream();
    input.Write(data, 0, data.Length);
    input.Position = 0;
    GZipStream gzip = new GZipStream(input, 
                      CompressionMode.Decompress, true);
    MemoryStream output = new MemoryStream();
    byte[] buff = new byte[64];
    int read = -1;
    read = gzip.Read(buff, 0, buff.Length);
    while(read > 0) {
      output.Write(buff, 0, read);
      read = gzip.Read(buff, 0, buff.Length);
    return output.ToArray();

You need to save this class in a .cs file and put it in the App_Code directory of your ASP.NET application, making sure it's contained in the proper custom namespace (if you don't specify any namespace, the class will be available in the built-in ASP namespace).

Compressing the ViewState

Now, we can actually compress the ViewState of the page. To do that, we have to override the two methods LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium. The code simply uses an additional hidden field, __VSTATE, to store the compressed ViewState. As you can see, by viewing the HTML of the page, the __VIEWSTATE field is empty, while our __VSTATE field contains the compressed ViewState, encoded in Base64. Let's see the code.

Collapse Copy Code
public partial class MyPage : System.Web.UI.Page {

  protected override object LoadPageStateFromPersistenceMedium() {
    string viewState = Request.Form["__VSTATE"];
    byte[] bytes = Convert.FromBase64String(viewState);
    bytes = Compressor.Decompress(bytes);
    LosFormatter formatter = new LosFormatter();
    return formatter.Deserialize(Convert.ToBase64String(bytes));

  protected override void SavePageStateToPersistenceMedium(object viewState) {
    LosFormatter formatter = new LosFormatter();
    StringWriter writer = new StringWriter();
    formatter.Serialize(writer, viewState);
    string viewStateString = writer.ToString();
    byte[] bytes = Convert.FromBase64String(viewStateString);
    bytes = Compressor.Compress(bytes);
    ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));

  // The rest of your code here...

In the first method, we just decode from Base64, decompress and deserialize the content of the __VSTATE, and return it to the runtime. In the second method, we perform the opposite operation: serialize, compress, and encode in Base64. The Base64 string is then saved into the __VSTATE hidden field. The LosFormatter object performs the serialization and deserialization tasks.

You may also want to create a new class, for example, CompressedPage, inheriting from System.Web.UI.Page, in which you override the two methods and then inherit your page from that class, for example MyPage : CompressedPage. Just remember that .NET has only single inheritance, and by following this way, you "spend" your only inheritance chance to use the ViewState compression. On the other hand, overriding the two methods in every class is a waste of time, so you have to choose the way that best fits your needs.

Performances and Conclusions

After a few tests, I noticed that the ViewState has been reduced from 38 KB to 17 KB, saving 44%. Supposing you have an average of 1 postback per minute per user, you could save more than 885 MB of bandwidth per month on every single user. That's an excellent result: you save bandwidth (and therefore money), and the user notices a shorter server response time.

I wanted to point out that this solution has a performance hit on the server's hardware. Compressing, decompressing, encoding, and decoding data is quite a heavy work for the server, so you have to balance the number of users with your CPU power and RAM.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



关于View State的行为的实验


rnrn为解决HTTP请求无状态化给服务器端编程造成的不便,ASP.NET页面通过名字为”__VIEWSTATE”的隐蔽FORM字段(hidden form field)实现页面视图状态的持久化。这个机制的具体行为在Microsoft的文档里没有提及,但是,我们可以通过页面将页面trace属性设置为true,对其进行一定程度的跟踪分析。本人通过一个简单的aspx页面,观察到了关于View State机制的一些有趣的规律。rnrn考察下面的aspx页面的执行情况rnrnFile Name: probe.aspxrnrn<%@ Page language="c#" trace="true" %>rn rnrn Probe View State Behaviorrnrnrn rnrnrn rnrnrnrnrn执行跟踪片断:rnControl TreernControl Id Type Render Size Bytes (including children) Viewstate Size Bytes (excluding children)rn__PAGE ASP.teststate_aspx 567 20rn _ctl0 System.Web.UI.LiteralControl 77 0rn Label1 System.Web.UI.WebControls.Label 26 28rn _ctl1 System.Web.UI.LiteralControl 7 0rn form1 System.Web.UI.HtmlControls.HtmlForm 437 0rn _ctl2 System.Web.UI.LiteralControl 11 0rn Label2 System.Web.UI.WebControls.Label 37 0rn _ctl3 System.Web.UI.LiteralControl 10 0rn TextBox1 System.Web.UI.WebControls.TextBox 61 0rn _ctl4 System.Web.UI.LiteralControl 10 0rn submit System.Web.UI.WebControls.Button 66 0rn _ctl5 System.Web.UI.LiteralControl 8 0rn _ctl6 System.Web.UI.LiteralControl 20 0rnrnrn这个跟踪结果表明:rn1、 TextBox不会产生VIEWSTATE跟踪数据,因为TextBox()的数据在POST时是自动包含在request body里面的,估计其他的可以映射为类HTML元素的WebControl的行为应该也如此;也就是说,Text会“自我”维持状态,不论是否将其EnableViewState设置为true。rn2、 Label不必包含在 元素里面也可以通过内的__VIEWSTATE字段维持状态;rn3、 一个Control被维持状态,前提必须是其状态(不仅是Text或Value属性)被Server代码引用,即使EnabelViewState已经设置为true。Label2的属性没有被引用,故没有__VIEWSTATE数据,而在Page_Load函数里引用了Label1.Text,故出现了28个字节的__VIEWSTATE数据。由此可以看出,.ASP.NET不会盲目地、囫囵吞枣地使用State Persistence,而是在只有必要是才使用。rn4、 可以映射为类HTML元素的WebControl只能在内使用,但象Label这样的非类WebControl则可以在页面的任何地方使用。HTMLInputControl则可以使用在以外(显然状态不能Persist),使用在内则自动通过request body进行State Persistence,和System.Web.UI.WebControls.TextBox的行为相同。rnrn 论坛