{
const string CompressedViewStateKey = "__COMPRESSEDVIEWSTATE";
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 = CompressHelper.Compress(data);
string str = Convert.ToBase64String(compressedData);
ClientScript.RegisterHiddenField(CompressedViewStateKey, str);
}
protected override object LoadPageStateFromPersistenceMedium()
{
string viewState = Request.Form[CompressedViewStateKey];
byte[] data = Convert.FromBase64String(viewState);
byte[] uncompressedData = CompressHelper.Decompress(data);
string str = Convert.ToBase64String(uncompressedData);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(str);
}
}
这个方法重写了 Page.SavePageStateToPersistenceMedium 和 Page.LoadPageStateFromPersistenceMedium 方法。代码注册了一个 __COMPRESSEDVIEWSTATE 的隐藏字段,把压缩的 ViewState 放在其中,不再使用原先的 __VIEWSTATE 字段。
我要做的一个页面的情况是,顶部有很多选择查询参数的控件,用户首先输入参数,再点击搜索按钮后系统会把搜索出来的记录集显示在 UpdatePanel 里的 GridView 上;若不选择参数, GridView 上会显示数据库中所有的记录集。使用上面的代码在未启用局部刷新时没有问题,但是启用的话,假如用户第一次选择了一些参数,搜索,GridView 会绑定显示搜索出来的记录集,但是这时点击 GridView 的分页按钮, GridView 重新显示的却是所有记录集,也就是说, ViewState 丢失了。
禁用 Ajax 或不用上面的方法压缩 ViewState ,都可以恢复正常,但如果我两个都想要呢?好在这世上有 Google 这东西,我搜索到了下面的解决方案:
{
protected override void SavePageStateToPersistenceMedium(object state)
{
Pair pair;
PageStatePersister persister = this.PageStatePersister;
object viewState;
if (state is Pair)
{
pair = (Pair)state;
persister.ControlState = pair.First;
viewState = pair.Second;
}
else
{
viewState = state;
}
LosFormatter formatter = new LosFormatter();
StringWriter writer = new StringWriter();
formatter.Serialize(writer, viewState);
string viewStateStr = writer.ToString();
byte[] data = Convert.FromBase64String(viewStateStr);
byte[] compressedData = CompressHelper.Compress(data);
string str = Convert.ToBase64String(compressedData);
persister.ViewState = str;
persister.Save();
}
protected override object LoadPageStateFromPersistenceMedium()
{
PageStatePersister persister = this.PageStatePersister;
persister.Load();
string viewState = persister.ViewState.ToString();
byte[] data = Convert.FromBase64String(viewState);
byte[] uncompressedData = CompressHelper.Decompress(data);
string str = Convert.ToBase64String(uncompressedData);
LosFormatter formatter = new LosFormatter();
return new Pair(persister.ControlState, formatter.Deserialize(str));
}
}
如果用 Reflector 看看 System.Web.UI.Page 类的 SavePageStateToPersistenceMedium 和 LoadPageStateFromPersistenceMedium 方法,你会发现上面的代码和微软的实现差不多,都是使用 Persister.Save 和 Persister.Load 来保存和获取 ViewState (只是上面的代码加上了压缩和解压的逻辑),这里的 Persister 是默认的 HiddenFieldPageStatePersister ,所以页面还是使用 __VIEWSTATE 字段来保存 ViewState。
改成第二段代码来压缩 ViewState 就正常了。至于第一段代码会在 Update Panel 中出问题,我是这样猜的:
启用 UpdatePanel 的局部刷新并不是真正的局部刷新,只不过微软做了点手脚,用 XMLHttpRequest 对象去向服务器提交请求,而服务器毫不知情,还是会生成一个完整的页面生成周期,把生成的 HTML 完整的返回,这时 ScriptManager 把不在 UpdatePanel 里的内容统统去掉,只接收 UpdatePanel 里面的内容,然后在客户端刷新一下,造成局部刷新的“假像”。问题就出在用 XMLHttpRequest 对象去请求服务器的时候,ScriptManager 不知道我们把 ViewState 放在 __COMPRESSEDVIEWSTATE 字段中,而用的是 __VIEWSTATE 字段里的内容,所以服务器会认为用户没有输入查询参数,返回了数据库中的所有记录……
问题好像就是这样产生的,不过我还有点不清楚,Persister.ControlState 和 Persister.ViewState 各是什么意思, MSDN 上也没说太明白,哪位大虾解释一下?
参考