对于大多数程序员而言,viewstate就是页面中的名字叫做__VIEWSTATE的隐藏控件,它使我们页面体积膨胀,不利于页面的seo优化;页面返送间都要携带更多的大量的数据,耗费更多的流量、延长响应时间。它使得我们很焦虑:禁用它,甚至不再使用web form,转向MVC,在MVC还没推出之前,甚至是转向其它的平台。。。
先不说那种取舍的对错,自己还是很喜欢web form这套框架的,看看页面的生命周期,它把整个页面的创建工业化、流水线化,它能让我们很容易的造一个”航母”出来,相比来说mvc就像一个手工作坊。但是对于web这种轻量级的应用,mvc显然更加适合,web form反而有点杀鸡牛刀的感觉。
如果你也同样喜欢web form,你必须对让web form超神,同时也可能超鬼的viewstate相当的了解。关于如何超鬼,开头已经说了;如何超神的呢,我们都知道http是一种无状态的协议,也就是我们的每次请求实际上都有创建一个网页的新实例,viewstate这个一级技能就能让页面满状态原地复活(岂能不超神。。。),从而节省一些数据读取时间和资源,其次它不像session、cache那像持续占用服务器内存,在页面实例创建完毕之后,就会被清理,节省服务器资源。
viewstate的前世今生
从MSDN中我们可以知道一个页面生命周期大约可分为为:页请求、开始、初始化、加载、验证、回发事件处理、呈现、卸载这几个阶段。
引用MSDN的一个图:
1,我们可以看到在 OnInit 和 OnInitComplete方法之间有一个TrackViewSate的方法,它开启对视图状态更改的跟踪。也就是说,从这里开始,你才能使用viewstate(否则这些数据将不会被发送回页面),页面开始建立所有控件状态的一个快照。
2,加载阶段OnPreLoad之前的LoadViewState方法,如果页面是回发的,他就会使用页面回发的viewstate,利用它记录的信息改变对应控件的属性,让你页面原状态复活。
3,在页面呈现之前的预呈现阶段,我们会看到一个SaveViewState的方法,在这里它会检查页面的所有控件的属性,如果和之前建立的快照有不一样的,就会记录下来。在此之后的控件的属性修改也不会被记录下来,回送到页面。
4,最后这些记录的信息会序列化为Base64编码的字符串,做为一个隐藏的字段的值发回到页面中。
合理使用viewstate
知道了viewstate怎么来的,我们就可以什么时候使用viewstate,怎么避免产生viewstate。
1,如果控件的值就不会发生变化,可以静态文本出现,不要在程序里为它赋值。
2,如果一些控件需要回发中被重新填充数据或值会根据用户的输入而改变的,都没必要使用。
3,如果一个页面根本不存在回发或者一个简单的回发可以使用其它ajax等去实现的,果断禁用此页的viewstate。
如何禁用viewstate,这个简单,就是在web.config或页面上加一个EnableViewState设为false即可。
有点注意的是,你如果要只对页面的某个控件禁用viewstate,你可以对页面开启EnableViewState而只对这个控件设置EnableViewState禁用就OK了;
但是如果你只对某个控件使用viewstate,对页面EnableViewState禁用和控件EnableViewState开启是达不到效果的。
因为页面的EnableViewState=”false”会强制的把整个页面及其控件的禁用。
想要达到这种效果,我们需要用到另一个ViewStateMode的属性,我们要对页面设置EnableViewState=”true”,然后通过ViewStateMode=“Disabled”来禁用页面的viewstate,而控件默认的ViewStateMode=“Inherit”,我们再去设置需要启用viewstate控件的ViewStateMode=“Enabled”来只启用某个控件使用viewstate。
但是这里还有一点我们要注意的是,我们做一个试验,我们新建一个页面,页面很简单就放几个asp:Label,并且在Page_Load中给这几上label赋值,并且给设置页面的Trace="true",也就是开启页面的跟踪。
第一次设置页面 EnableViewState="true",浏览,首先页面源代码,可以看到页面id="__VIEWSTATE" 的input有了一大段的数据,在页面的跟踪信息的ControlTree表格上,可以看到每个控件是viewstate占用了多少空间。
第二次我们设置页面 EnableViewState="false",结果一定想得到,页面代码中id="__VIEWSTATE" 的input虽然还有数据,但明显少了很多,页面的跟踪信息中数据也发生了变化就是各个控件的viewstate已经是0了。
最后设置EnableViewState="true" ,但是同时设置页面ViewStateMode="Disabled",并且还设置页面中一个label的 ViewStateMode="Disabled",这样其实也可以起到禁用页面viewstate的作用,浏览再看一下结果:页面代码中id="__VIEWSTATE" 的input还是很少的一点,就像是设置EnableViewState="false"一样,但是对于跟踪的信息呢?
这个数据却又像只是设置了EnableViewState="true"一样,细心的童鞋可能会发现其实有点不一样那就是表格的最顶行的_page的viewstate没有占用空间,但是label的空间依然是有的,尽管我们设置了其中的一个ViewStateMode="Disabled"。
通过这个小试验,我们可以发现ViewStateMode="Disabled"不会使得页面流程中停止快照的建立和预呈现的对比,并且记录这个信息,它只是没有把这些个信息发送到页面,也就是说它虽然节省了往返带宽,但是服务器其实还是进行了整个viewstate信息的流程的,只是在最后一步将其遗弃。
安全性
首先一点是我们可以在web.config中设置pages的maxPageStateFieldLength来设置单个viewstate的最大值,以防止一些代理、防火墙或移动浏览器因为一个隐藏字段体积过大而阻止页面。
其次再说的,我们页面中看到的viewstate字符串,其实是没有加密的,只是经过了Base64的编码,我们可以很easy的逆向出来看到原始的数据。不过还好的是,asp.net默认启用了一个基于散列码来保证viewstate不被修改:asp.net会根据消息验证代码 (MAC) 密钥去计算viewstate,得到一个散列码,然后把这个码添加到viewstate中,保存到页面的隐藏字段中;如果页面回发时,再次计算散列码与之前的比较,如果不匹配就拒绝接受此次回发。
默认情况下,这个散列的密钥是根据计算机的MAC地址计算的,如果在web场中,就可能会不能验证viewstate。这种环境下,我们可以通过EnableViewStateMac=”false”来禁用校验;另一种方法就是在多台服务器间配置同样的自己生成的密钥,然后分布在不同的计算机的machine.config中配置。
即使这样,我们viewstate还是很容易别人看到,如果不想这样,我们可以用ViewStateEncryptionMode来加密viewstate,它有三个值:Always,总是加密;Never,从不加密;Auto,仅在需要时加密,在呈现Html之前显式调用Page.RegisterRequiresViewStateEncryption()来启用加密,否则不加密。
结语
不同的场景,有不同的需求,只有充分的了解才能让我们在使用上合理取舍,取长避短。