chengbo的.Net学习日记(Study DotNet)

对待生活,应该怀着一颗感恩的心

首页 新随笔 联系 订阅 管理
  96 Posts :: 16 Stories :: 244 Comments :: 2 Trackbacks
为了节省带宽,可能需要压缩一下 ViewState ,具体压缩的代码,网上一搜一大把,比如下面这段:
public class CompressedViewStatePage : System.Web.UI.Page
{
    
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 这东西,我搜索到了下面的解决方案:

public class CompressedViewStatePage : System.Web.UI.Page
{
    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 上也没说太明白,哪位大虾解释一下?

参考

posted on 2007-05-02 18:09 chengbo 阅读(2995) 评论(7)  编辑 收藏 所属分类: Web developmentDotNet

Feedback

#1楼  2007-05-02 19:43 Roach [未注册用户]
个人觉的VIEWSTATE压缩意义不大,用户多时反而增加服务器压力
  回复  引用    

#2楼  2007-05-02 21:18 Jeffrey Zhao      
嗯,解决方案是对的,不过UpdatePanel实现原理有一点点不对,服务器返回的不是完整的HTML然后再经过筛选,而是服务器直接返回客户段可以识别的字符串。
其实这再次说明了“标准扩展”的重要性,尽可能的不要使用tricky的方法。你一开始遇到的问题其实就是因为你改变了页面处理ViewState的方法,自行注册了一个新的ViewState的Hidden Field到页面中去而并非改变页面所认为的ViewState。
如果人人都按照标准办事,那么世界将会多么美好啊,呵呵。:)
  回复  引用  查看    

#3楼 [楼主] 2007-05-02 21:27 chengbo      
@Jeffrey Zhao
谢谢指教:)
  回复  引用  查看    

如果是同步处理肯定没有问题

如果是放在UpdatePanel处理时,第一次Get请求,__COMPRESSEDVIEWSTATE被写入了Html页

当第二次您进行异步回调,在_OnFormSubmitCompleted回调时,Asp.net Ajax是不会对__COMPRESSEDVIEWSTATE这个东西的进行回填的,它只会处理_OnFormSubmitCompleted预定义的类型,所以异步处理时您提交的
__COMPRESSEDVIEWSTATE虽然有值,但使终是第一次Get请求的值,因此使用第一种方式序列化后的ViewState是没有对您有意义的值的,GridView就没法还原的.....

对于第二种方法需要二次序列化
因为最后一步persister.Save,.net会自动的把ControlState,ViewState,封装成Pair序列化,从性能上来说不来合算



  回复  引用    

#5楼  2007-05-03 02:59 Jeffrey Zhao      
@Scott.X.Liu
其实这里的问题(性能问题先不考虑),只是异步回送时Register的HiddenField没有输出到客户端而已。
为什么呢?因为异步回送时通过ClientScriptManager的输出是无效的。
该怎么解决这个问题呢?就是使用ScriptManager.RegisterHiddenField来替换上面的ClientScript.RegisterHiddenField方法试试看吧。:)
  回复  引用  查看    

赵老大一语点破呀,扩展一下

ScriptManager.RegisterHiddenField方法分为二部分
1
Page.ClientScript.RegisterHiddenField是ajax为了扩展asp.net处理的

2.如果是异步时,
调用
ScriptRegistrationManager.RegisterHiddenFieldInternal()该方法会向ScriptRegistrationManager.ScriptHiddenFields赋值


------------------------------------------------------------
在PageRequestManager.ProcessScriptRegistration()时,会对
ScriptRegistration.RegisterHiddenFieldInternal注册的ScriptHiddenFields发送回客户端
  回复  引用    

#7楼  2007-11-26 23:37 YCCS [未注册用户]
稍微改寫一下,把它改為VB.NET的版本,應該會有人需要吧!
http://subocheng.blogspot.com/2007/11/viewstateupdatepanel-vbnet.html
  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  博客园首页

  新闻频道

  社区

  小组

  博问

  网摘

  闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      


相关链接: