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

ViewState 压缩

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (69投票s)

2006年7月10日

CPOL

3分钟阅读

viewsIcon

438295

如何压缩 ASP.NET 页面的 ViewState 并节省带宽。

引言

最近,我开发了一个巨大的 ASP.NET 页面,包含超过 30 个控件。 众所周知,禁用实际不需要 ViewState 的控件(例如LiteralLabel)的 ViewState 是个好主意。 完成后,我注意到隐藏的 ViewState 字段仍然有几 KB 大。 这对于仍然没有宽带连接的用户来说显然是一个大问题,因为向服务器上传 40 KB 确实是一个糟糕的问题,特别是当他们开始一遍又一遍地单击“提交”按钮时,因为他们没有注意到任何响应。 因此,在互联网上搜索了几次后,我构建了一个简单的解决方案来压缩 ViewState,从而节省大约 50% 的带宽。 Scott Hanselman 的这篇文章特别有用。 虽然可以使用外部库来执行压缩任务,但我认为更好的解决方案是使用 .NET Framework 2.0 包含的 GZipStreamDeflateStream

在内存中压缩和解压缩数据

首先,我们需要一种在内存中压缩和解压缩字节数组的方法。 我整理了这个简单的静态类,它公开了两个方法:CompressDecompress。 根据 MSDN,两个可用的类 GZipStreamDeflateStream 使用相同的算法,因此选择哪一个无关紧要。

下面的代码非常简单,不需要进一步解释

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);
    gzip.Close();
    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);
    }
    gzip.Close();
    return output.ToArray();
  }
}

您需要将此类保存在.cs 文件中,并将其放入 ASP.NET 应用程序的App_Code 目录中,确保它包含在适当的自定义命名空间中(如果您未指定任何命名空间,则该类将在内置的 ASP 命名空间中可用)。

压缩 ViewState

现在,我们可以实际压缩页面的 ViewState。 为此,我们必须覆盖两个方法 LoadPageStateFromPersistenceMediumSavePageStateToPersistenceMedium。 该代码只是使用一个额外的隐藏字段 __VSTATE 来存储压缩的 ViewState。 正如您所看到的,通过查看页面的 HTML,__VIEWSTATE 字段是空的,而我们的 __VSTATE 字段包含压缩的 ViewState,以 Base64 编码。 让我们看看代码。

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...
}

在第一个方法中,我们只需从 Base64 解码,解压缩并反序列化 __VSTATE 的内容,然后将其返回到运行时。 在第二个方法中,我们执行相反的操作:序列化、压缩并以 Base64 编码。 然后将 Base64 字符串保存到 __VSTATE 隐藏字段中。 LosFormatter 对象执行序列化和反序列化任务。

您可能还想创建一个新类,例如,CompressedPage,从 System.Web.UI.Page 继承,在其中覆盖这两个方法,然后让您的页面从该类继承,例如 MyPage : CompressedPage。 只需记住,.NET 只有单继承,通过这种方式,您“花费”了您唯一的继承机会来使用 ViewState 压缩。 另一方面,在每个类中覆盖这两个方法是浪费时间,因此您必须选择最适合您需求的方式。

性能和结论

经过一些测试,我注意到 ViewState 从 38 KB 减少到 17 KB,节省了 44%。 假设您每个用户平均每分钟有 1 个回发,则每个用户每月可以节省超过 885 MB 的带宽。 这是一个非常好的结果:您可以节省带宽(因此也节省了金钱),并且用户会注意到更短的服务器响应时间。

我想指出的是,该解决方案会对服务器的硬件造成性能影响。 压缩、解压缩、编码和解码数据对于服务器来说都是相当繁重的工作,因此您必须平衡用户数量与您的 CPU 功率和 RAM。

© . All rights reserved.