bitfan(数字世界一凡人)的专栏

在新浪博客的新家:http://blog.sina.com.cn/bitfan

用户操作
[即时聊天] [发私信] [加为好友]
金旭亮ID:bitfan
374634次访问,排名142好友0人,关注者60
=========
一名代码一写就十多年的“老古董级程序员”,看来还会再写下去;
一名有着“好为人师”臭毛病的小小讲师,“混迹于”大学校园,时不时发几句不合时宜的“反动言论”,好在皮厚,任人笑骂而无所谓;
于北京高楼林立之地租一小屋容身,依靠讲几节小课赚点小课时费应付房东大妈,所幸政府开恩照顾,终于让我等穷教师有了当房奴的资格,高呼:感谢政府!
==========
几本小书:
《网站建设教程》:高等教育出版社(2005)
《编程的奥秘——.NET软件技术学习与实践》:电子工业出版社(2006)
《.NET 2.0面向对象编程揭秘》:电子工业出版社(2007)。
《ASP.NET程序设计教程》高等教育出版社(2009)。

几堂小课:
在ITCAST(http://www.itcast.net)讲授.NET系列在线视频课程,想将课堂开到互联网上,目标不大,要帮助更多的年轻人学好技术找到好工作。野心不小,已录制了5个系列30节课,打算一路跟踪微软最新技术,打造国内自成体系、独特风格的微软技术系列课程。
bitfan的文章
原创 87 篇
翻译 0 篇
转载 0 篇
评论 2212 篇
最近评论
wjfmail:可以学,但估计要比别人多很多的努力.毕竟开发不是一种天赋就可以拥有的才能.你碰的钉子会比别人多得多.不过,还是从现在开始努力吧.
gsqswjh:我就是小学文化,而且"奔四"了,才开始学习编程,学如逆水行舟,不进则退,说的太对了,加油啊!
Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



文章分类
收藏
    相册
    .NET技术学习与实践
    5.2 使用.NET开发数据库应用程序
    数据绑定原理
    杂类
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 让人糊涂的TrackViewState()与视图状态保存收藏

    新一篇: 《编程的奥秘》及《.NET 2.0面向对象编程揭秘》两书配套光盘中的示例源码 | 旧一篇: 在火狐浏览器中设置FileUpload控件的宽度

    让人糊涂的TrackViewState()与视图状态保存

    在ASP.NET自定义控件开发中,如果需要自己实现控件的状态保存,一般都要实现SaveViewState(),LoadViewState()和TrackViewState()三个方法,这是由IStateManager接口所定义的。

    前两个方法作用很明晰,SaveViewState()是将控件的当前状态抽取为一个状态对象,页面类获取所有控件的状态对象对其进行编码生成可在网络上传输的格式(Base64),并将其塞入到一个id为__VIEWSTATE的input元素中发给浏览器。

     LoadViewState()是控件从浏览器中传回来的数据中重新读取值,使其回复到上次状态。

    最头痛的是TrackViewState(),它是干什么的?MSDN里的话如下: 

    Causes tracking of view-state changes to the server control so they can be stored in the server control's StateBag object. This object is accessible through the Control.ViewState property.   

    这个话无比正确,但却让人不怎么明白。它怎么就让控件跟踪视图状态的变化了?浏览器回发的数据包含了页面当前控件的值,而__VIEWSTATE隐藏域中包含了上次控件的状态,两者一比不就知道哪些变化了,用你TrackViewState()是干什么?

     Google了许多资料,几乎就没发现有价值的介绍,最后,终于在一篇文章《TRULY Understanding ViewState 》中发现了有价值的东西,它的网址如下:   http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx

    原来,为了减少在网络上的传输量,应该只保存“变化”的数据到视图状态中。

    所有Web控件大都派生自Control类,Control类有一个ViewState属性,它是一个StateBag类的对象。控件可以有多个属性,每个属性都有一个值,StateBag对象按照“Key-Item”格式管理这些数据,一般将属性名当作Key。Item则封装了对应属性的值(注意它是一个StateItem类型的对象)。

    StateBag对象保存的Item有一个IsDirty属性用于标识此Item是否有更改。为此,StateBag对象设置了一个标记,当此标记为true时,在给Item赋值时就会同步设置这一Item的IsDirty属性,通告外界——我的数据有变化。而这个内部标记就可以通过TrackView()方法进行设置。如果不设置这个内部标记,那么,不管怎样修改StateBag中的Item,这一Item其IsDirty属性始终都是false。

    可以设计一个简单的网页,然后用Reflector查看其生成的程序集源码。

    检看StateBag的源码,发现它有以下的代码说明这个内部标记名为marked.
    private bool marked;

    以下为StateBag的TrackViewState()的反汇编代码:

    internal void TrackViewState()
    {
        this.marked = true;
    }

    向ViewState中追加数据的方法本质上是通过StateBag的Add()方法实现的:

    public StateItem Add(string key, object value)
    {
     //Key不能为空
        if (string.IsNullOrEmpty(key))
        {
            throw ExceptionUtil.ParameterNullOrEmpty("key");
        }
        //根据Key查找集合中的数据对象
        StateItem item = this.bag[key] as StateItem;
        if (item == null)  //没有找到对应的数据对象
        {
            //如果传入的value不为空或要求跟踪,则创建一个对象加入到集合中
            if ((value != null) || this.marked)
            {
                item = new StateItem(value);
                this.bag.Add(key, item);
            }
        }
        else 
         //如果找到对应的数据对象

              if ((value == null) && !this.marked)
              {
                  this.bag.Remove(key); //值为空,且不要求跟踪,则从集合中移除此对象
               }
              else  //要求跟踪或者值不为空,设置对应的数据对象值
              {
                  item.Value = value;
               }

     //设定已更改标记
        if ((item != null) && this.marked)
        {
            item.IsDirty = true;
        }
        return item;
    }


    可以清楚地看到,如果标记被设置,IsDirty属性就返回true,否则,保持为false.
    注意:这里并没有比对原始值和传入的值是否相等再设置IsDirty属性。因此,只要标记被设置,任何对视图状态的非空赋值都被认为是Dirty的。
     
    因此,TrackView()方法其实就是设置了一个“请监控我的变化”的标记,调用此方法之后,任何对控件属性的改变都会被跟踪,这样一来,此控件的SaveViewState()方法在生成状态对象时就会将此属性的修改记录下来。简单地说:只有IsDirty=true的属性值才会被SaveViewState()方法处理。这就避免了为控件所有的属性都生成状态数据,大大减少了要保存的数据量。
    以下为StateBag类的SaveViewState()反汇编代码,可以清楚地看到其中使用了IsDirty属性。

    internal object SaveViewState()
    {
        ArrayList list = null;
        if (this.bag.Count != 0)
        {
            IDictionaryEnumerator enumerator = this.bag.GetEnumerator();
            while (enumerator.MoveNext())
            {
                StateItem item = (StateItem) enumerator.Value;
                if (item.IsDirty)
                {
                    if (list == null)
                    {
                        list = new ArrayList();
                    }
                    list.Add(new IndexedString((string) enumerator.Key));
                    list.Add(item.Value);
                }
            }
        }
        return list;
    }


    一切都清楚了。

    那么,到底控件的视图状态是怎样保存的?这涉及到页面的生命周期。

    当页面被装载时,它的ProcessRequest()方法被调用。在此方法中,会调用一个SaveAllState方法,此方法内部又调用SaveViewStateRecursive()方法(来自基类Control),
    SaveViewStateRecursive()先调用Control.SaveViewState()保存自己的数据,再递归地调用每个子控件的SaveViewStateRecursive()方法获取所有子控件的状态对象,然后一级级返回,最终得到整个页面的状态对象,紧接着将这一对象按Base64编码生成页面视图状态字串并放入到__VIEWSTATE隐藏域中(由Page.SavePageStateToPersistenceMedium方法完成)。

    综上所述:
    如果控件没调用TrackView()方法,那么,本次对控件属性的修改将不会被添加到__VIEWSTATE隐藏域中,因此,下次页面回发时,控件的属性将回复为默认值。
    理解这点还是有意义的,特别是在动态创建控件的情况下,请看以下这个典型示例:

     protected void Page_Load(object sender, EventArgs e)
        {
          
            CheckBoxList chk = new CheckBoxList();

            if (!IsPostBack)
            {
                chk.Items.Add("Hello");
            }
            form1.Controls.Add(chk);

        }

     在Page上扔个Button,以便可以PostBack。运行后Postback的结果,“Hello” item没被保留。
    改为:

    protected void Page_Load(object sender, EventArgs e)
        {
          
            CheckBoxList chk = new CheckBoxList();
            form1.Controls.Add(chk);

            if (!IsPostBack)
            {
                chk.Items.Add("Hello");
            }
          

        }
    或者:
     protected void Page_Load(object sender, EventArgs e)
        {
          
            CheckBoxList chk = new CheckBoxList();
            (chk.Items as IStateManager).TrackViewState();
            if (!IsPostBack)
            {
                chk.Items.Add("Hello");
            }
            form1.Controls.Add(chk);

        }
    都可以在多次回发时保证Hello项出现,并正确地恢复它的状态(选中还是不选中)。

    这个例子说明:将控件加入到页面类的Controls集合中时,会自动调用TrackViewState()方法。

    注意:Control.TrackViewState()方法是保护的,不允许外界调用。而动态创建Button等简单控件时,没有办法在页面类中直接调用TrackViewState()方法。因此,通过Controls.Add()方法间接调用TrackViewState()方法是唯一的选择。

    最后给出一条动态创建控件的原则:

    应该在new出控件对象之后,马上将其加入到父控件的Controls集合中,这样可以完全地保证它的状态能在回发时恢复。

    ============================
    为了弄明白这点小东西,先后看了MSDN无数遍,Google了N篇文章,又用Reflector反汇编,累晕了。

    再次感叹,ASP.NET框架内部的机制太复杂太庞大了,有的时候,还真是得“不求甚解”,不然,要弄清楚所有底层技术细节,恐怕胡须都白了。

    发表于 @ 2008年06月02日 21:22:13|评论(loading...)|编辑

    新一篇: 《编程的奥秘》及《.NET 2.0面向对象编程揭秘》两书配套光盘中的示例源码 | 旧一篇: 在火狐浏览器中设置FileUpload控件的宽度

    评论

    #zhy2002 发表于2008-06-03 13:21:17  IP: 123.234.80.*
    也就是说,写在服务器端标记中的attribute的值是在trackviewstate调用之前放到viewstate里的
    #liu_binq63 发表于2008-06-05 11:48:41  IP: 60.26.23.*
    汗.........
    看懂了70%,还需要好好研究研究才行啊。
    #whwqs 发表于2008-06-05 13:43:38  IP: 219.140.166.*
    谢谢楼主,让我理解了往返保持状态的机制,少走弯路
    #worininainai 发表于2008-06-06 18:24:14  IP: 61.187.8.*
    写的太精辟了
    辛苦了,查这些资料应该费了不少时间
    谢谢了

    MSN:wmj2212@126.com
    请您加我
    #myshijieye 发表于2008-06-09 16:26:44  IP: 125.120.3.*
    高压断路器
    高压开关柜
    开关柜
    玻璃钢
    #ISpace2008 发表于2008-06-19 11:14:26  IP: 221.224.52.*
    收藏了,还有待消化消化
    #carlos_xq 发表于2008-07-10 01:22:24  IP: 219.143.205.*
    收藏了
    #quzhoushijie 发表于2008-07-26 09:00:47  IP: 60.176.251.*
    杭州百度推广
    杭州网站优化
    杭州百度代理
    杭州百度优化
    杭州Google优化
    杭州白马网络
    杭州网站推广
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © bitfan(数字世界一凡人)