您的位置:首页 > 编程语言 > ASP

ASP.NET ViewState详解

2013-05-17 09:34 441 查看
[b]概述[/b]

ViewState是一个被误解很深的动物了。我希望通过此文章来澄清人们对ViewState的一些错误认识。为了达到这个目的,我决定从头到尾详细的描述一下整个ViewState的工作机制,其中我会同时用一些例子说明我文章中的观点,结论。比如我会用静态控件(declaredcontrols)和动态控件(dynamiccontrols)两个方面来说明同一个问题。

现在有关ViewState的文章可谓多如牛毛,你可能会说再写有关ViewState的文章无异于炒剩饭(我这篇文章便是:D)。但是我却不这么认为,如果把ViewState看成一匹野马的话,那么这匹野马并没有死去,它还活跃的很,说不定这个时候它正在你的客厅里撒野呢。所以我们有必要再次去把它击倒。不过你也不需要担心,从这篇文章你可以发现其实这匹马也没有那么坏。

我的意思并不是否然目前还没有好好说明ViewState的文章,只是我总觉得好像这些文章都缺少一些东西,而这些缺少的东西往往就会导致人们对ViewState的困惑。比如:理解ViewState是怎样跟踪那些已经出现变化的数据(dirtydata)就非常重要,但是很多文章却没有过多的涉及,或者即便涉及了可能其中却包含了错误的信息。比如这篇文章(W3Schools)中就说页面回传的值也是保存在ViewState中的,但是这个观点是错误的。不信是吗?那么你在一个页面上放置一个TextBox控件和一个Button控件,然后你在“属性”中将TextBox的EnableViewState设置为False,然后通过点击Button回传页面,你会发现TextBox还是仍旧会保留你输入的值,而不会如你想象的由于TextBox的ViewState被禁用了而导致TextBox的值在页面回传的过程中消失了。还有一些文章(#1GoogleSearchResult,ASP.NETDocumentationonMSDN)描述了服务器控件如何在页面的回传中保持自身状态。这些文档虽然没有全错,但是有些描述还是存在一些不准确的地方,如:


"IfacontrolusesViewStateforpropertydatainsteadofaprivatefield,thatpropertyautomaticallywillbepersistedacrossroundtripstotheclient."

(如果一个控件用ViewState而不是用类的私有字段(privatefield)来存储数据,那么这些控件属性的值将会自动在页面回传之间保持状态??[这句话的意思有待确定])


上面这句话似乎在暗示任何东西只要是保存在ViewState状态包(StateBag)中,那么就会在服务器和客户端页面回传的过程中被传递。(ThatseemstoimplythatanythingyoushoveintotheViewStateStateBagwillberound-trippedintheclient'sbrowser.NOTTRUE!)不对!所以说对于ViewState控件的困惑还是存在的。而且在Internet上我目前还没有找到一篇100%准确完整的描述ViewState工作的文章。我目前找到的最好文章是这一篇(thisonebyScottMitchell)。这篇文章是值得一读的。然而这篇文章还是有些美中不足,它没有描述在控件初始化和ViewState跟踪的时候父控件和子控件之间的关系。而恰恰就是这一点就会导致对ViewState大量的误解,至少我是有过这种经历的。

根据上面的情况,这篇文章从最开始先会对ViewState实现的功能进行一个完全描述。然后对ViewState的实现进行一个详细的阐述,在这个过程中我会同时举一些相对应的例子,通常我会先举一个开发人员经常会犯的错误的例子,然后再举一个例子来表示如何修正错误。在这里我需要实现声明一下的是,虽然在ASP.NET2.0中ViewState的实现机制有稍许变动,但是在写这篇文章的时候我依然是以ASP.NET1.x的版本为前提进行的。比如说在ASP.NET2.0的新增了一个新类型的ViewState--ControlState,但是实际上ControlState和ViewState并不大变,所以我们这里可以忽略它。

首先让我们看看为什么深入了解ViewState是如此重要。

对ViewState的误解可能导致...

导致一些敏感信息被泄漏;

针对ViewState的攻击(akatheJediMindTrick,aka是又称作,又叫做的意思。JediMindTrick,看过星球大战的人对于Jedi一定不陌生,Jedi就是绝地武士。JediMindTrick是绝地武士的一个招式,可以用于控制对方的思维。有关这个的具体知识可以参见:http://gollum.easycp.de/gollum/gollum.php?a=core&l=en&wl=en&q=这里作者估计是表达了通过ViewState的攻击来达到控制对方的目的。比如一个等离子电视的价格被修改为了1美元一台)

很差的性能,在某些极端的情况下可能根本就没有性能。

并发性差--想象一下如果每次回传的数据都有50kB,那么你的服务器能承受多少并发的访问量呢?

糟糕的全局设计;

以上结果一定会让你抓狂的(头痛,反胃,头昏眼花,皱眉头...)。

如果你正在开发基于ASP.NET平台的网络应用程序,并且你无视ViewState存在的话,那么以下的情况可能会发生在你的身上。

ViewState["Key1"]=123.45M;//storeadecimalvalue
ViewState["Key2"]="abc";//storeastring
ViewState["Key3"]=DateTime.Now;//storeaDateTime



实际上ViewState仅仅就是一个定义在System.Web.UI.Control类中的一个保护类型(Protected)

的属性名称。由于所有服务器端的控件,用户自定义控件还有页面(Page)类都是继承自System.Web.UI.Control类,

所以这些控件都具有这些属性。ViewState的真正类型实际应该是System.Web.UI.StateBag类。

严格的说,虽然StateBag类虽然定义在System.Web的命名空间下,实际上StateBag类

和ASP.NET并没有严格上的依存关系,它也完全可以放在System.Collections命名空间下。

事实上许多服务器端控件大多数属性值都是利用ViewState来进行数据存储。你可能认为

TextBox.Text属性是按如下形式存储的:


publicstringText
publicstringText
publicclassMyClass
publicstringText
stateBag.IsItemDirty("key");//returnsfalse
stateBag["key"]="abc";
stateBag.IsItemDirty("key");//stillreturnsfalse

stateBag["key"]="def";
stateBag.IsItemDirty("key");//STILLreturnsfalse

stateBag.TrackViewState();
stateBag.IsItemDirty("key");//yupstillreturnsfalse

stateBag["key"]="ghi";
stateBag.IsItemDirty("key");//TRUE!

stateBag.SetItemDirty("key",false);
stateBag.IsItemDirty("key");//FALSE!

看到上面的例子应该很清楚了,在调用了TrackViewState()方法后,StateBag开始跟踪

其所包含项值的变化。再次无论你如何修改StateBag中项的值,都无法把数据弄“脏”

的。而且这里还需要注意一点,在TrackViewState()方法调用后,只要是出现了赋值操作

那么就会使其被标记为脏数据,StateBag并不会判断赋值前后对应项的值是否出现了变化。

如下例子所示:


stateBag["key"]="abc";
stateBag.IsItemDirty("key");//returnsfalse

stateBag.TrackViewState();
stateBag["key"]="abc";
stateBag.IsItemDirty("key");//returnstrue

可能你会认为
根据赋值前后ViewState是否存在变化然后再标记是否是脏数据这样更加符合常理。但是必须注意的是ViewState的项是可以存储任何类型的值的
(实际上任何赋值给ViewState的变量都会被装箱为Object类型的变量),所以比较赋值前后的值是否一致实际上并没有变面上看的那么容易。而且
不是每种类型都是先了IComparable的接口,所以通过调用CompareTo方法来进行比较也是不可行的。另外还有一个原因,我们知道
ViewState还需要将其内部的数据进行序列和反序列化,当这些操作发生后,你得到的对象已经不是原来那个对象了,所以比较对象之间的引用也是无法完
成的。基于以上这些原因,ViewState采取了一种简单的做法,也就意味着ViewState的数据变化跟踪也是一个简要的跟踪。


这里你可能会想在设计StateBag类的时候为什么要使其具备跟踪数据变化的能力呢?我们为什么要跟踪那些出现变化的项呢?(Whyonearth
wouldanyoneneedtoknowonlychangessinceTrackViewState()is
called?Whywouldn'ttheyjustutilizetheentirecollectionofitems?
),这个疑问往往是造成对ViewState困惑的根源。基于这个问题我曾经和很多人交谈过,其中不乏有着多年ASP.NET开发经验的专家,但是很遗
憾,我没有从任何一个人那里得到我满意的答案。没有一个人能够解释清楚为什么StateBag对数据变化的跟踪是必要的。为了解释清楚这个问题,我们首先
需要先了解一下ASP.NET是怎样建立静态控件的。所谓静态控件(declarative
control)就是那些从页面或者用户自定义控件的源码中可以看到声明代码的控件。如:

<asp:Labelid="lbl1"runat="server"Text="HelloWorld"/>

这里在页面上声明了一个Label控件。然后ASP.NET会解析这段代码,它首先会查找那些标签中带有“runat=server”的代码,然后根据类型创建对应类型的控件对象,接着将标签中设置的控件属性值一个一个的赋值到控件实例对象中。比如例子中我们设置了Label对象的Text属性,那么在解析的时候就会存在一个类似于:lbl1.Text="HelloWorld"的赋值过程。通过反射机制,ASP.NET可以知道对应的类型是否具有对应的属性,对应的属性是什么数据类型。这里Text属性的数据类型是String型,对于数据类型不是String的属性,那么在设置属性前ASP.NET必须实现将String到对应数据类型的转换。如:TextBox控件可以设置Width属性,但是Width是Unit类型的,所以这里就设计到一个从String到Unit类型的转化过程。

好,到了这,我们再以前面所说的内容将当前发生的事情再描述一遍。我们已经知道大多数服务器控件的属性值最终是存储在ViewState中。而且如果ViewState已经开始了跟踪数据,那么此次属性的赋值就会导致“脏数据”的产生,但是如果ViewState还没有开始跟踪数据,那么脏数据的标记值就一直为False。现在问题就是在当前ASP.NET解析静态控件的时候是否开始跟踪和是否产生了“脏数据”呢?答案是,没有。原因是此时的ViewState赋值之前ASP.NET并没有去调用TrackViewState()方法,所以ViewState是不会对数据的更改进行跟踪的。事实上ASP.NET是在页面生命周期的OnInit阶段才调用TrackViewState()方法的。这样做的目的就是让ASP.NET可以很方便的区分控件的哪些属性值在初次声明后仍未改变,那些属性值已经被改变了(可能是程序的方式也可能是人工输入的方式)。如果到目前为止你还没有意识到这个观点很重要的话,那么请继续往下读吧。

3.序列化和反序列化(SERIALIZATIONANDDESERIALIZATION)
我们先把ASP.NET怎样解析生成静态控件放一边,我们前面提到的ViewState的两个重要功能(1.ViewState可以像HashTable那样通过名值对来存储值;2.ViewState可以对那些修改的数据进行跟踪。)现在我们将来讨论另外一个话题,那就是ASP.NET是怎样通过StateBag类的特性来实现那些看似诡异的功能的。

如果你在ASP.NET中使用过ViewState,事实上我相信只要是
ASP.NET的开发者都会使用过ViewState了。而且可能你也知道了序列化(serialization)的问题。如果是默认的方式,那么
VIewState中的值会被序列化成一个基于Base64编码的字符串,然后存储在页面中一个叫做_ViewState的隐藏变量中。

这里在继续之前,我需要稍稍叉开一下话题先说一些页面的控件树。我发现有不少有多
年工作ASP.NET开放经验的程序员还不知道控件树的存在。由于他们仅仅是对.aspx页面进行操作,所以他们仅仅只关心那些页面上声明的控件。但是我
们必须认识到页面的控件实际是以一颗控件树存在的,并且控件中还可以包含子控件。这颗控件树的根节点就是页面本身(Page),然后树的第二层通常是包含
3个控件,它们分别是用于保存表单(<form>)标签前所有信息的文本控件(Literal),然后是表单控件(Form),然后是表单
(</form>)标签后面的所有信息的文本控件(Literal)。接着是树的第三层包含的控件就是在表单标签内声明的那些控件,如果这些
控件中还包含子控件,那么这颗控件树的深度将会不断的加深,一直到所有页面的控件都被包含在这颗控件树中。每个控件都会有自己的ViewState对象,
并且由于这些控件共同的基类(System.Web.UI.Control)中包含一个受保护(protected)的方法SaveViewState,
方法的返回值是一个Object变量。在Control.SaveViewState方法中如果发现ViewState不为空,那么就直接调用其私有变量
_viewState(StateBag类型)的SaveViewState方法。通过阅读这个方法,可以发现其作用就是将ViewState中被标记为
脏数据(dirty)的项的键和值都存储在一个ArrayList中,然后再将这个ArrayList进行返回。通过递归的方法遍历整个控件树的各个节
点,并递归的调用各个控件的SaveViewState方法,这样当整个控件树被遍历完成以后,那么和控件树一一对应的会形成一个由ViewState的
值组成的数据树。

在这个阶段,ViewState中存储的数据还没有被转化为我们在
_ViewState隐藏变量中存储的Base64编码的字符串。这里仅仅是形成了一颗需要被持久化存储的数据树。这里再强调一下,存储的数据是
ViewState中那些被标记为Dirty的项。StateBag类具有跟踪功能就是为了在存储的时候判断哪些数据需要被存储,哪些数据不需要被存储(实际上这是StateBag具有跟踪数据功能的唯一原因)。
很聪明是吧,但是如果使用不当的话,在ViewState中依然可能保存一些不必要的数据。我会在后面的例子中来说明这些可能犯的错误。(.That
istheonlyreasonwhyithasit.Andohwhatagoodreasonitis--
StateBagcouldjustprocesseverysingleitemstoredwithinit,butwhy
shoulddatathathasnotbeenchangedfromit'snatural,declarative
statebepersisted?There'snoreasonforittobe--itwillbe
restoredonthenextrequestwhenASP.NETreparsesthepageanyway
(actuallyitonlyparsesitonce,buildingacompiledclassthatdoes
theworkfromthenon).Despitethissmartoptimizationemployedby
ASP.NET,unnecessarydataisstillpersistedintoViewStateallthetime
duetomisuse.Iwillgetintoexamplesthatdemonstratethesetypesof
mistakeslateron.)

突击测试(POPQUIZ)

果你已经读到了这里,那么祝贺你,我要奖励一下这么有毅力的你。咱们来个突击测试如何?我是不是人很好呢?哈哈。题目是这样的,我们有两个几乎一模一样
的.aspx页面,我们分别称之为Page1.aspx和Page2.aspx,每个页面都存在一个form,其中包含一个Label控件,如下所示:

<formid="form1"runat="server">
<asp:Labelid="label1"runat="server"Text=""/>
</form>

这两个页面唯一的区别是Label中包含的值不同(Label.Text的值)。Page1.aspx中的

label1.Text="abc",如下代码所示。


<asp:Labelid="label1"runat="server"Text="abc"/>

那么对于Page2.aspx中的Label,我们对其多赋一点值(就来个美国宪法的序言吧)。如下代码所示。

<asp:Labelid="label1"runat="server"Text="WethepeopleoftheUnitedStates,inordertoformamoreperfectunion,establishjustice,insuredomestictranquility,provideforthecommondefense,promotethegeneralwelfare,andsecuretheblessingsoflibertytoourselvesandourposterity,doordainandestablishthisConstitutionfortheUnitedStatesofAmerica."/>

现在我们在浏览器中运行Page1.aspx,那么我们将看到一个abc。然后你通过浏览器查看页面的HTML源码,你可以找到那个“臭名昭著”的隐藏字段(_ViewState)。然后把Page1.aspx的_ViewState值保留下来。接着运行Page2.aspx,同样保留其_ViewState的值。然后比较这两个ViewState的大小(注意:这里比较的是大小,或者说比较字符串的长度,而不是内容)。问题来了,请问这两个ViewState的大小是否一样呢?好了,在公布答案之前我们再去看看另外一个问题,我们在两个页面上都增加一个Button控件,这样通过点击Button按钮我们就可以回传页面了。一下就是页面中声明Button控件的代码:

<asp:Buttonid="button1"runat="server"Text="Postback"/>

这个Button并没有任何的Click事件处理函数,仅仅用于将页面提交服务器。我们再重复上面的实验,唯一不同的是,我们这回是在点击了Button后再去查看各自得_ViewState的值,我们的问题还是一样的,请问这两个ViewState的大小是否一样呢?好,现在揭晓正确答案。第一个问题的答案是:是的,两个页面的ViewState的大小是一样的。原因是这当前这两个ViewState中并不包含任何和Label有关的数据。前面我们知道所有需要存储在_ViewState中的数据都必须是被标记为Dirty的脏数据。而需要启动对ViewState中各项数据的跟踪,必须先要调用TrackViewState()方法,什么时候调用TrackViewState方法呢?是在页面生命周期中的OnInit阶段,而由于Label控件中的Text是在页面中静态声明的,所以在AddParsedSubObject阶段(早于OnInit阶段)Text值就已经被赋值到对应Label控件中了。所以这些Text中的值将不会被标记为Dirty,同时也不会被保存在_ViewState中。所以无论Label.Text有什么不同,那么其页面的_ViewState始终是相同的。那页面中那一小段的_ViewState到底包含了什么信息呢?你可以用ViewStateDecoder工具查看一下,可以发现在这样一个简单的界面,_ViewState仅仅包含了页面的哈希代码(HashCode)。

好,让我们到第二个问题(页面加了Button那个情况),答案同样是:是的,它们的大小也是一样的。原因和上面解释的一样。简单的说就是在TrackViewState()方法后面并没有对Label.Text属性进行赋值操作,所以ViewState中的项并没有被标记为Dirty,自然就不会被序列化并记录到_ViewState隐藏变量中了。

到此为止我们已经基本了解了ASP.NET平台是怎样决定一个数据是否需要被序列化并永久保留在_ViewState中了(那些被标记为Dirty的数据)。至于ASP.NET是怎样序列化这些数据的已经不是本文的范围了,如果你有兴趣进一步了解的话,那么请参看如下两篇文章:LosFormatterforASP.NET1.x和ObjectStateFormatterforASP.NET2.0。

在这一个小节的最后,我们要简单的说说反序列化。反序列化和序列化是相对应的,如果不能通过反序列化来讲序列化的对象进行还原,进行进行操作的话,那么序列化操作将没有任何意义。但是这是另外一个话题,所以这里就不再进行赘述。

4.自动恢复数据(AUTOMATICALLYRESTORESDATA)

此为止我们已经说到了ViewState最后一个功能,那就是自动恢复数据。有些文章将这个过程和上面提到的反序列化过程混淆在一起,这样的理解是不正确
的,实际上自动恢复数据的过程并不是反序列化过程的一部分。ASP.NET首先反序列化_ViewState中的值,将其还原为对象,然后再将这些还原的
值重新赋值给其对应的控件。

作为所有控件包括Page类基类的System.Web.UI.Control类
型中包含一个LoadViewState(object
savedState)方法。其中需要被载入的数据就是通过参数savedState进行传递的。LoadViewState和前面所说的
SaveViewState是相对应的方法。而且和SaveViewState方法类似的是,Control.LoadViewState也是简单的调用
了StateBag中的LoadViewState方法。通过查看LoadViewState的源代码可以发现,这个函数实际就是将savedState
中存储的名值对重新Add到StateBag列表中(StateBag.Add(key,
value))。同时我们从LoadViewState也可以发现在.NETFramework1.1中
传入的object变量是一个pair类型的变量。pair类型包含两个属性First,
Second都是object类型的变量,在ViewState中其中一个属性存储的是包含ViewState.Item.Key的ArrayList而
另外一个属性包含的是ViewState.Item.Value的ArrayList,相对应的Key和Value在ArrayList中的下标相同。然
后StateBag类就通过遍历两个ArrayList将值添加到状态项中(注意在.NETFramework2.0中这个方法的实现有些小小的改动,放弃使用Pair类型而仅仅使用一个ArrayList,ArrayList中每个名值对占两个Item,前一个为key后一个为value,循环的时候以步进2进行循环)。这里需要注意的是从LoadViewState()重新载入到ViewState的数据仅仅包含前一次请求被标记为Dirty的那些数据(注意不是当次请求(currentrequest),而是前一次请求(previousrequest)就是当前请求的前一次请求。)
在载入_ViewState中包含的数据之前,对应控件的ViewState中可能已经包含了一些值了,比如那些静态控件中预先声明好的值
(如:<asp:Label
Text="abc"/>中的Text属性在LoadViewState()之前就已经是"abc"了)。如果LoadViewState()中需
要载入的数据中已经存在值了,那么对应的值将被新值所覆盖。

为了让大家有一个完整的认识,这里将页面回传以后发生的事情再简单的描述一下。首
先页面回传以后,整个Page将重新生成并且那些页面上声明的静态控件也都已经被解析添加到以Page为根节点的控件树中,那些静态控件对应的静态声明的
属性值也都被初始化。然后是OnInit阶段,在这个阶段ASP.NET会调用TrackViewState方法,从此以后所有对控件属性的赋值操作都将
导致被跟踪。接着就是LoadViewState()方法被调用,这里那些从_ViewState中反序列化出来的值将被重新赋给对应的控件,由于在此之
前TrackViewState()已经被调用了,_ViewState中包含的数据对应的属性值都会被标记为Dirty。这样当调用
SaveViewState的时候,这些属性值还是会被持久的保留到_ViewState中,这样在页面的一次次回传和页面一次次的重新建立的过程中,这
些控件的值就被保留下来了。现在是不是有种豁然开朗的感觉?恭喜你,你现在已经是一个ViewState管理的小小专家了:)。

一些常见的ViewState使用错误(IMPROPERUSEOFVIEWSTATE)

目前为止我们已经大致了解了ViewState运行机制了,我们可以再次回顾一下我们在使用ViewState中的一些错误,然后分析其原因。有些错误在
你了解了ViewState以后是显而易见,但是有些错误却比较隐蔽,但是通过对这些错误的深入分析将会让你对ViewState有进一步的了解。

错误使用ViewState的情况(CASESOFMISUSE)

为服务器端控件赋默认值(ForcingaDefault);

持久化静态数据(Persistingstaticdata);

持久化廉价数据(Persistingcheapdata);

以编码的方式初始化子控件(Initializingchildcontrolsprogrammatically);

以编码的方式创建控件(Initializingdynamicallycreatedcontrolsprogrammatically)。

1.为服务器端控件(webcontrol)设置默认值(ForcingaDefault)

注:这里我个人认为原文的例子存在问题,所以我这里按照自己的理解来谢。大家如果看了原文有不同的理解的话,欢迎和我进行交流。

这个错误是开发服务器端控件(WebControl)中最常见的错误,不过这个错
误修改起来非常的简单,而且修改后的代码会更加的简洁明了(事情往往就是这样,约正确的方式,越优的方式往往也是最简明的方式。besimpleis

good)。造成这种错误的原因往往是开发人员没有了解ViewState的跟踪机制或者根本就不知道有跟踪机制这种说法。我们来看一个例子,我们现在需
要一个空间,这个控件有一个Text属性,如果没有对Text进行赋值,那么就从一个Session变量中得到其默认值。我们的程序员Joe写下了如下代
码:

publicclassJoesControl:WebControl
<abc:JoesControlid="joe1"runat="server"/>

当Jane查看其页面HTML源代码的时候,她发现她的页面ViewState的体积也变大了。天哪,要知道Jane的页面上仅仅只有Joe的那个控件了。还了,你知道世界上的男女关系啦,Jane肯定是去让Joe去修改他这个蹩足的控件了,不过让人高兴的是这回Joe修改后的控件似乎工作的很好了。这就是Joe的第二次实现方式:

publicclassJoesControl:WebControl
<abc:JoesControlid="joe1"runat="server"Text="ViewStaterocks!"/>

这样和后面的实现方式在现实上也是没有区别的。因为这里并没有执行this.Text=Session["SomeSessionKey"]这个语句,自然this.Text并不认为出现了变化,那么ViewState["Text"]并不会被标记为Dirty,所以也不会被序列化到_ViewState中。现在我们讨论一下如果没有设置Text属性初值的情况,那么这个时候就会在JoesControl的OnLoad方法中执行this.Text=Session["SomeSessionKey"]这个语句,但是这个时候各个控件已经执行完成了OnInit阶段,所以TrackViewState()已经调用,这个时候this.Text已经被标记为Dirty了,所以会被持久化到_ViewState隐藏变量中,这样就增加了ViewState的大小。那么如果使用了第二种方法,判断是否设置了初值,如果没有那么就通过Session["SomeSessionValue"]中的默认值替代,这个阶段是在生成JoesControl(NewJoesControl)的时候进行赋值的,这个时候由于还未到达OnInit阶段,所以TrackViewState()方法还没有被调用,所以ViewState["Text"]并不会被标记为Dirty,当然也就不会记录到_ViewState中进行持久化。所以第二种实现方式是优于第一种实现方式的。

2.持久化静态数据(Persistingstaticdata)

我们这里所说的静态数据是那些不会被改变的数据(neverchange)或者在页面的生命周期中、一个用户会话中不会被改变的数据。
还是我们可爱的程序员Joe,最近他又接到了一个改造网站的任务,在他们公司的eCommerce网站上显示那些已经登录的用户,比如“嗨,XXXX,欢
迎回来!”Joe的前提条件是这个网站已经有了一个业务层的API,可以通过CurrentUser.Name的方法方便的得到当前已经验证的用户姓名。
剩下的把这个人名显示到页面上的工作就看Joe的了。以下是Joe的代码:

(ShoppingCart.aspx)
<!--用于显示登录用户姓名的Label控件-->
<asp:Labelid="lblUserName"runat="server"/>



(ShoppingCart.aspx.cs)
//用于在Label中动态显示登录用户姓名的代码;
protectedoverridevoidOnLoad(EventArgsargs)
<asp:Labelid="lblUserName"runat="server"EnableViewState="false"/>

好了,问题解决了。但是是否有更加好的解决方法呢?有!Label控件可能是
ASP.NET中最最被高估的控件了。这个可能是由于那些WinForm的VB编程者,在WinForm中如果要显示一些文本信息,你可能需要一个
Label。而ASP.NET中的这个Label可能被认为和WinForm中的Label是等价的了。但是真的就是这样的吗?通过HTML源码我们可以
看到Label控件实际被解析成了HTML中的<span>标签。你必须问问你自己是否真的需要这个<span>标签呢?如果不
需要涉及到特定的格式,仅仅是显示信息那么我觉得答案是否定的。请看:

<%=CurrentUser.Name%>

恩,这样你就可以避免生成一个<span>标签了,并且可以很好的解决问题。但是从编程习惯上来说,这种将前台和后台代码混合的形式是不提倡的,这样会使代码的可读性下降,并且使开发的职责无法明确区分。所以这里还可以使用一种ASP.NET中存在但是确被Label控件的光环笼罩的控件--Literal。这个控件仅仅将其Text中的内容输出到客户端,并且不会生成<span>标签。是不是觉得对这个控件有些印象,对了,前面在说道将页面解析成一个控件树的时候,第二层一般由三个控件组成,一个是Literal,用于存储到<form>标签以前的所有html代码。就是这个控件。以下就是使用Literal控件来替代Label控件的方法。当然这里也需要将EnableViewState设置为false。问题解决了的同时,我们节省了网络传输的资源。不错!

<asp:Literalid="litUserName"runat="server"EnableViewState="false"/>



3.持久化廉价的数据(Persistingcheapdata)

这个问题实际上包含了第一个问题。静态数据往往是很容易就可以得到的(取得的开销

/成本比较小),但是并不是所有容易取得的数据都是静态数据。可能这些数据会不停

的被更改,但是总体来说得到这些数据的成本很低。一个典型的例子是美国各个州的列表。

除非你要回到1787年12月7日(here),那么当前美国的所有州列表在短期内是不会有改变的。

当然我们现在的程序员都很痛恨硬编码。“让我把美国各个州的列表都静态的写在页面

上?傻子才这样做呢。”我们更加倾向于将州名都保留在一个数据库(或者其他易于

修改的配置文件中。),这样如果州名或者州的列表出现了任何变化,就不用修改源

代码了。恩,我完全同意这一点,我们的著名程序员Joe也是这样认为的,而且这张表

在他们公司已经存在了,表名叫做USSTATES,这回Joe的任务就是和操作这张表有关系的。

下面是用于显示美国各个州列表的下拉菜单(DropDownList):


<asp:DropdownListid="lstStates"runat="server"DataTextField="StateName"DataValueField="StateCode"/>


这里显示的是绑定从数据库中取得的美国州列表的数据代码:


protectedoverridevoidOnLoad(EventArgsargs)
<asp:DropdownListid="lstStates"runat="server"DataTextField="StateName"DataValueField="StateCode"EnableViewState="false"/>

protectedoverridevoidOnInit(EventArgsargs)
<asp:Labelid="lblDate"runat="server"/>

protectedoverridevoidOnInit(EventArgsargs)
<asp:Labelid="Label1"runat="server"Text="<%=DateTime.Now.ToString()%>"/>

你可能也有过这样尝试,但是ASP.NET会给你当头一棒,它会明确的告诉你<%=%>语法不能对服务器端控件的属性进行赋值操作。当然Joe也可以使用<%#%>的方法,但是这个方法和我们前面提到的禁用Label的ViewState同时在每次请求页面的时候绑定数据的方法实际上是一样的。问题是我们希望通过编码的方式为Label的Text属性的初值进行赋值操作(我们不希望这些赋值操作导致ViewState大小的增加),同样在以后的操作中我们希望这个Label控件依然可以像一个普通的Label控件被使用。简单的说就是这样,我们需要一个Label,它的默认值是当前的日期和时间,但是如果我们人工的对其Label.Text进行了赋值操作,那么我们还是希望这个值在页面的回传之间可以保留(即通过ViewState进行持久化)。举个简单的例子,Joe的页面上有一个按钮,当用户点击这个按钮那么显示当前日期和时间的Label将显示一个“空时间”(即:“--/--/------:--:--”),此按钮的响应代码为:

privatevoidcmdRemoveDate_Click(objectsender,EventArgsargs)
<asp:Labelid="Label2"runat="server"OnInit="lblDate_Init"/>

同样在后台编写Label.OnInit事件对应的响应函数并对Label.Text赋初值也是可以的。

2.创建用户自定义组件(Createacustomcontrol):

publicclassDateTimeLabel:Label
publicclassJoesCustomControl:Control
publicclassJoesCustomControl:Control
publicclassJoesCustomControl:Control


{


protectedoverridevoidOnInit(EventArgsargs)


{


DropDownListstates=newDropDownList();


states.DataSource=this.GetUSStatesFromDatabase();


states.DataBind();


this.Controls.Add(states);


}


}

这样做的好处还有,由于DropDownList的EnableViewState=true,所以DropDownList依然可以触发诸如OnSelectedIndexChanged事件。你也可以对同样的方法操作DataGrid控件,但是可能对于使用DataGrid的排序(sorting),分页(paging)还有SelectedIndex属性还是存在问题??(这几个问题还没有考究过)

ViewState和平共处(BEVIEWSTATEFRIENDLY)

到目前为止,如果你理解了这篇文章中所说的东西那么恭喜你,你已经知道ViewState是怎样实现其功能的了。知道了ViewState的工作原理我们就可以写出更加优化的代码,而往往这些更优的代码比那些蹩足的代码更加简洁明了。Enjoyit!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: