ViewState 压缩






4.87/5 (69投票s)
如何压缩 ASP.NET 页面的 ViewState 并节省带宽。
引言
最近,我开发了一个巨大的 ASP.NET 页面,包含超过 30 个控件。 众所周知,禁用实际不需要 ViewState 的控件(例如Literal
或 Label
)的 ViewState 是个好主意。 完成后,我注意到隐藏的 ViewState 字段仍然有几 KB 大。 这对于仍然没有宽带连接的用户来说显然是一个大问题,因为向服务器上传 40 KB 确实是一个糟糕的问题,特别是当他们开始一遍又一遍地单击“提交”按钮时,因为他们没有注意到任何响应。 因此,在互联网上搜索了几次后,我构建了一个简单的解决方案来压缩 ViewState,从而节省大约 50% 的带宽。 Scott Hanselman 的这篇文章特别有用。 虽然可以使用外部库来执行压缩任务,但我认为更好的解决方案是使用 .NET Framework 2.0 包含的 GZipStream
或 DeflateStream
。
在内存中压缩和解压缩数据
首先,我们需要一种在内存中压缩和解压缩字节数组的方法。 我整理了这个简单的静态类,它公开了两个方法:Compress
和 Decompress
。 根据 MSDN,两个可用的类 GZipStream
和 DeflateStream
使用相同的算法,因此选择哪一个无关紧要。
下面的代码非常简单,不需要进一步解释
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。 为此,我们必须覆盖两个方法 LoadPageStateFromPersistenceMedium
和 SavePageStateToPersistenceMedium
。 该代码只是使用一个额外的隐藏字段 __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。