合理利用ViewState
一、ViewState基本原理
ViewState 为 ASP.NET 开发人员解决了一个特定问题 —— 保留服务器端的控件的状态。这很重要,因为 ASP.NET 中的大部分服务器端控件模型是根据这样一个假设生成的,那就是 —— 如果用户回发到相同页面,所有控件保持其状态不变。
二、ViewState的问题
ViewState虽然使Web开发人员的工作变得简单,但也会带来一些问题。通常,在一个Web服务器返回的页面中会发现如下类似的代码:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJMzM4Njg4NjEzD2QWAgIBD2QWEgIBDxYCHgNzcmMFFi4uL2lmbHlpbWFnZXM />
这就意味着每次回发时这些数据不管有用还是没用都要回传到服务器,很容易想到,这将给网络造成一定的压力。然而,有些开发人员却没有引起足够的重视,有些看似简单的页面中,ViewState竟然达到了几十上百K,也就是说每次回发时都相当于上传了一个几十K的文件,另外对这些数据进行序列化和编码也将消耗服务器大量资源。所以如果不加以重视,ViewState对性能的影响将是巨大的。
三、禁用不必要的ViewState
默认情况下,Web服务器控件的ViewState是开启的,可以通过设置EnableViewState=”false”禁用某控件的ViewState,也可以通过来禁用此页面上所有控件的ViewState。
当然,并不是所有的Web服务器控件都应该禁用ViewState,如果使用不当,可能会导致一些意想不到的错误。在下列情况下,ViewState通常是可以被禁用的:
1. 页面不回传给自身。
2. 处理的不是控件的事件。
3. 控件没有动态的或数据绑定的属性值(或对于每一个请求它们都设置在代码中)。
特别注意:GridView是ViewState的重量级用户,默认情况下,一个很小的表格都会生成大量的ViewState,所以要特别注意。通常情况下,若仅仅是显示数据而不用作排序、分页、编辑等操作,一般是可以禁用的。
四、压缩ViewState
然而为了实现某些功能,有些页面或控件的ViewState是必须开启的,那么有没有其它办法能解决此问题呢?答案是肯定的。实现方法如下:
1. 添加引用:ICSharpCode.SharpZipLib.dll,点此下载此压缩组件。
2. 在App_Code中增加CompressedViewState.cs,类CompressedViewState继承自System.Web.UI.Page,然后重写用于处理ViewState的LoadPageStateFromPersistenceMedium和SavePageStateToPersistenceMedium方法。代码如下:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;
using ICSharpCode.SharpZipLib.Zip.Compression;
/// <summary>
/// CompressedViewState 的摘要说明
/// </summary>
public class CompressViewState : System.Web.UI.Page
{
/// <summary>
/// 设定序列化后的字符串长度为多少后才启用压缩,此处设置为5K
/// </summary>
private static Int32 LimitLength = 5120;
/// <summary>
/// 设定压缩比率,压缩比率越高性消耗也将增大
/// </summary>
private static Int32 ZipLevel = ICSharpCode.SharpZipLib.Zip.Compression.Deflater.BEST_COMPRESSION;
/// <summary>
/// 重写保存页的所有视图状态信息
/// </summary>
/// <param name="pViewState">要在其中存储视图状态信息的对象</param>
protected override void SavePageStateToPersistenceMedium(Object pViewState)
{
//实现一个用于将信息写入字符串的 TextWriter
StringWriter mWriter = new StringWriter();
//序列化 Web 窗体页的视图状态
LosFormatter mFormat = new LosFormatter();
//将有限对象序列化 (LOS) 格式化的对象转换为视图状态值
mFormat.Serialize(mWriter, pViewState);
//将序列化对象转成Base64字符串
String vStateStr = mWriter.ToString();
//设置是否启用了压缩方式,默认情况下为不启用
Boolean mUseZip = false;
//判断序列化对象的字符串长度是否超出定义的长度界限
if (vStateStr.Length > LimitLength)
{
//对于长度超出阶线的进行压缩,同时将状态设为压缩方式
mUseZip = true;
Byte[] pBytes = Compress(vStateStr);
//将字节数组转换为Base64字符串
vStateStr = System.Convert.ToBase64String(pBytes);
}
//注册在页面储存ViewState状态的隐藏文本框,并将内容写入这个文本框
ClientScript.RegisterHiddenField("__MSPVSTATE", vStateStr);
//注册在页面储存是否启用压缩状态的文本框,并将启用状态写入这个文本框
ClientScript.RegisterHiddenField("__MSPVSTATE_ZIP", mUseZip.ToString().ToLower());
}
/**/
/// <summary>
/// 对字符串进行压缩
/// </summary>
/// <param name="pViewState">ViewState字符串</param>
/// <returns>返回流的字节数组</returns>
private static Byte[] Compress(String pViewState)
{
//将存储状态的Base64字串转换为字节数组
Byte[] pBytes = System.Convert.FromBase64String(pViewState);
//创建支持内存存储的流
MemoryStream mMemory = new MemoryStream();
Deflater mDeflater = new Deflater(ZipLevel);
ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream mStream = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(mMemory, mDeflater, 131072);
mStream.Write(pBytes, 0, pBytes.Length);
mStream.Close();
return mMemory.ToArray();
}
/// <summary>
/// 重写将所有保存的视图状态信息加载到页面对象
/// </summary>
/// <returns>保存的视图状态</returns>
protected override Object LoadPageStateFromPersistenceMedium()
{
//使用Request方法获取序列化的ViewState字符串
String mViewState = this.Request.Form.Get("__MSPVSTATE");
//使和Request方法获取当前的ViewState是否启用了压缩
String mViewStateZip = this.Request.Form.Get("__MSPVSTATE_ZIP");
Byte[] pBytes;
if (mViewStateZip == "true")
{
pBytes = DeCompress(mViewState);
}
else
{
//将ViewState的Base64字符串转换成字节
pBytes = System.Convert.FromBase64String(mViewState);
}
//序列化 Web 窗体页的视图状态
LosFormatter mFormat = new LosFormatter();
//将指定的视图状态值转换为有限对象序列化 (LOS) 格式化的对象
return mFormat.Deserialize(System.Convert.ToBase64String(pBytes));
}
/// <summary>
/// 解压缩ViewState字符串
/// </summary>
/// <param name="pViewState">ViewState字符串</param>
/// <returns>返回流的字节数组</returns>
private static Byte[] DeCompress(String pViewState)
{
//将Base64字符串转换为字节数组
Byte[] pBytes = System.Convert.FromBase64String(pViewState);
ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream mStream = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(new MemoryStream(pBytes));
//创建支持内存存储的流
MemoryStream mMemory = new MemoryStream();
Int32 mSize;
Byte[] mWriteData = new Byte[4096];
while (true)
{
mSize = mStream.Read(mWriteData, 0, mWriteData.Length);
if (mSize > 0)
{
mMemory.Write(mWriteData, 0, mSize);
}
else
{
break;
}
}
mStream.Close();
return mMemory.ToArray();
}
3. 在页面中使用ViewState压缩。在需要压缩页面.cs文件中继承此类即可。
说明:
1.因为压缩数据会占用服务器资源,所以如果ViewState本身就很小,压缩是没有太大意义的,甚至会加重服务器负担,所以在以上代码中设置只有当ViewState大于5K时才启用压缩。
2.依此原理,也可重写LoadPageStateFromPersistenceMedium和SavePageStateToPersistenceMedium方法将ViewState保存在数据库或服务器磁盘中。
3.如果采用最大压缩率的方式,通常可以将ViewState的体积减小50%以上。
五、总结
ViewState 没有什么神秘之处,它只是利用了一个老的 Web 编程技巧:在一个隐藏的窗体字段中来回传递状态,并将它直接应用于页面处理框架中。但效果却非常好 - 在基于 Web 的窗体中只需编写并维护很少的代码。 但是,用户可能并不总是需要它,强烈建议在不需要时禁它,这样可能会给你的Web应用带来很大的性能提升。
ViewState 为 ASP.NET 开发人员解决了一个特定问题 —— 保留服务器端的控件的状态。这很重要,因为 ASP.NET 中的大部分服务器端控件模型是根据这样一个假设生成的,那就是 —— 如果用户回发到相同页面,所有控件保持其状态不变。
二、ViewState的问题
ViewState虽然使Web开发人员的工作变得简单,但也会带来一些问题。通常,在一个Web服务器返回的页面中会发现如下类似的代码:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJMzM4Njg4NjEzD2QWAgIBD2QWEgIBDxYCHgNzcmMFFi4uL2lmbHlpbWFnZXM />
这就意味着每次回发时这些数据不管有用还是没用都要回传到服务器,很容易想到,这将给网络造成一定的压力。然而,有些开发人员却没有引起足够的重视,有些看似简单的页面中,ViewState竟然达到了几十上百K,也就是说每次回发时都相当于上传了一个几十K的文件,另外对这些数据进行序列化和编码也将消耗服务器大量资源。所以如果不加以重视,ViewState对性能的影响将是巨大的。
三、禁用不必要的ViewState
默认情况下,Web服务器控件的ViewState是开启的,可以通过设置EnableViewState=”false”禁用某控件的ViewState,也可以通过来禁用此页面上所有控件的ViewState。
当然,并不是所有的Web服务器控件都应该禁用ViewState,如果使用不当,可能会导致一些意想不到的错误。在下列情况下,ViewState通常是可以被禁用的:
1. 页面不回传给自身。
2. 处理的不是控件的事件。
3. 控件没有动态的或数据绑定的属性值(或对于每一个请求它们都设置在代码中)。
特别注意:GridView是ViewState的重量级用户,默认情况下,一个很小的表格都会生成大量的ViewState,所以要特别注意。通常情况下,若仅仅是显示数据而不用作排序、分页、编辑等操作,一般是可以禁用的。
四、压缩ViewState
然而为了实现某些功能,有些页面或控件的ViewState是必须开启的,那么有没有其它办法能解决此问题呢?答案是肯定的。实现方法如下:
1. 添加引用:ICSharpCode.SharpZipLib.dll,点此下载此压缩组件。
2. 在App_Code中增加CompressedViewState.cs,类CompressedViewState继承自System.Web.UI.Page,然后重写用于处理ViewState的LoadPageStateFromPersistenceMedium和SavePageStateToPersistenceMedium方法。代码如下:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;
using ICSharpCode.SharpZipLib.Zip.Compression;
/// <summary>
/// CompressedViewState 的摘要说明
/// </summary>
public class CompressViewState : System.Web.UI.Page
{
/// <summary>
/// 设定序列化后的字符串长度为多少后才启用压缩,此处设置为5K
/// </summary>
private static Int32 LimitLength = 5120;
/// <summary>
/// 设定压缩比率,压缩比率越高性消耗也将增大
/// </summary>
private static Int32 ZipLevel = ICSharpCode.SharpZipLib.Zip.Compression.Deflater.BEST_COMPRESSION;
/// <summary>
/// 重写保存页的所有视图状态信息
/// </summary>
/// <param name="pViewState">要在其中存储视图状态信息的对象</param>
protected override void SavePageStateToPersistenceMedium(Object pViewState)
{
//实现一个用于将信息写入字符串的 TextWriter
StringWriter mWriter = new StringWriter();
//序列化 Web 窗体页的视图状态
LosFormatter mFormat = new LosFormatter();
//将有限对象序列化 (LOS) 格式化的对象转换为视图状态值
mFormat.Serialize(mWriter, pViewState);
//将序列化对象转成Base64字符串
String vStateStr = mWriter.ToString();
//设置是否启用了压缩方式,默认情况下为不启用
Boolean mUseZip = false;
//判断序列化对象的字符串长度是否超出定义的长度界限
if (vStateStr.Length > LimitLength)
{
//对于长度超出阶线的进行压缩,同时将状态设为压缩方式
mUseZip = true;
Byte[] pBytes = Compress(vStateStr);
//将字节数组转换为Base64字符串
vStateStr = System.Convert.ToBase64String(pBytes);
}
//注册在页面储存ViewState状态的隐藏文本框,并将内容写入这个文本框
ClientScript.RegisterHiddenField("__MSPVSTATE", vStateStr);
//注册在页面储存是否启用压缩状态的文本框,并将启用状态写入这个文本框
ClientScript.RegisterHiddenField("__MSPVSTATE_ZIP", mUseZip.ToString().ToLower());
}
/**/
/// <summary>
/// 对字符串进行压缩
/// </summary>
/// <param name="pViewState">ViewState字符串</param>
/// <returns>返回流的字节数组</returns>
private static Byte[] Compress(String pViewState)
{
//将存储状态的Base64字串转换为字节数组
Byte[] pBytes = System.Convert.FromBase64String(pViewState);
//创建支持内存存储的流
MemoryStream mMemory = new MemoryStream();
Deflater mDeflater = new Deflater(ZipLevel);
ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream mStream = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(mMemory, mDeflater, 131072);
mStream.Write(pBytes, 0, pBytes.Length);
mStream.Close();
return mMemory.ToArray();
}
/// <summary>
/// 重写将所有保存的视图状态信息加载到页面对象
/// </summary>
/// <returns>保存的视图状态</returns>
protected override Object LoadPageStateFromPersistenceMedium()
{
//使用Request方法获取序列化的ViewState字符串
String mViewState = this.Request.Form.Get("__MSPVSTATE");
//使和Request方法获取当前的ViewState是否启用了压缩
String mViewStateZip = this.Request.Form.Get("__MSPVSTATE_ZIP");
Byte[] pBytes;
if (mViewStateZip == "true")
{
pBytes = DeCompress(mViewState);
}
else
{
//将ViewState的Base64字符串转换成字节
pBytes = System.Convert.FromBase64String(mViewState);
}
//序列化 Web 窗体页的视图状态
LosFormatter mFormat = new LosFormatter();
//将指定的视图状态值转换为有限对象序列化 (LOS) 格式化的对象
return mFormat.Deserialize(System.Convert.ToBase64String(pBytes));
}
/// <summary>
/// 解压缩ViewState字符串
/// </summary>
/// <param name="pViewState">ViewState字符串</param>
/// <returns>返回流的字节数组</returns>
private static Byte[] DeCompress(String pViewState)
{
//将Base64字符串转换为字节数组
Byte[] pBytes = System.Convert.FromBase64String(pViewState);
ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream mStream = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(new MemoryStream(pBytes));
//创建支持内存存储的流
MemoryStream mMemory = new MemoryStream();
Int32 mSize;
Byte[] mWriteData = new Byte[4096];
while (true)
{
mSize = mStream.Read(mWriteData, 0, mWriteData.Length);
if (mSize > 0)
{
mMemory.Write(mWriteData, 0, mSize);
}
else
{
break;
}
}
mStream.Close();
return mMemory.ToArray();
}
3. 在页面中使用ViewState压缩。在需要压缩页面.cs文件中继承此类即可。
说明:
1.因为压缩数据会占用服务器资源,所以如果ViewState本身就很小,压缩是没有太大意义的,甚至会加重服务器负担,所以在以上代码中设置只有当ViewState大于5K时才启用压缩。
2.依此原理,也可重写LoadPageStateFromPersistenceMedium和SavePageStateToPersistenceMedium方法将ViewState保存在数据库或服务器磁盘中。
3.如果采用最大压缩率的方式,通常可以将ViewState的体积减小50%以上。
五、总结
ViewState 没有什么神秘之处,它只是利用了一个老的 Web 编程技巧:在一个隐藏的窗体字段中来回传递状态,并将它直接应用于页面处理框架中。但效果却非常好 - 在基于 Web 的窗体中只需编写并维护很少的代码。 但是,用户可能并不总是需要它,强烈建议在不需要时禁它,这样可能会给你的Web应用带来很大的性能提升。