开篇:经历了上一篇《aspx与服务器控件探秘》后,我们了解了aspx和服务器控件背后的故事。这篇我们开始走进WebForm状态保持的一大法宝—ViewState,对其刨根究底一下。然后,再对曾经很流行的ASP.Net AJAX方案中的利器—UpdatePanel这个神奇的区域一探究竟。
一、隐藏的状态—ViewState探秘
1.1 从Http的无状态说起
Http是一个无状态协议,同一个会话的连续两个请求互相不了解,它们由最新实例化的环境进行解析,除了应用本身可能已经存储在全局对象中的所有信息外,该环境不保存与会话有关的任何信息。另外,因为,浏览器和服务器之间是通过Socket进行通信,Http请求通常请求完毕就会关闭Socket连接,因此Http协议不会保持连接。如果保持连接会降低客户端并发处理请求数,不保持连接又会降低处理速度(建立连接所需的时间会长一点);
PS:这里我们可以这样来理解:假如我们去一个大型商场购物购买某个产品,第一次去的时候是A销售员接待了我们,带领我们来到XX产品的柜台并为我们推荐了XX产品;等我们回去使用XX产品后,觉得XX产品真心不错。第二次我们又去,但是这次却找不到上次那个A销售员了,相反商场分配了另一个B销售员来接待我们,他不知道我们上次选择了XX产品,相反它却一个劲地向我们推荐YY产品并把我们带向YY产品的柜台;这个时候,我们一般会说:我擦,把上次那个妹子给我叫来!
基于Http协议的无状态特性,我们在ASP.Net的开发中也会经常碰到这种情况:用户上一次提交的东西,下次再提交时服务器就不记得了。很多时候,我们感到很不解?后来,我们发现原来每一次的请求服务器都开启了不同的线程来处理,也就是说每次都会new一个XXX.aspx.cs中的类对象实例来进行处理(上一次new出来为我们处理的page对象也许早就被服务器销毁了)。比如,我们在xxx.aspx.cs代码中写入了一个int类型的number成员(初始为0),每次请求我们都想让这个number自增一下,然后重新返回给浏览器。但就是这么一个简单的梦想,我们却无法轻易的实现。
那么,到底怎么来破呢?大神们已经为我们想好了策略,我们可以使用隐藏域字段、Cookie、Session等来保存状态。而伟大的Microsoft还在ASP.Net中帮我们封装了ViewState,以至于我们在WebForm中进行PostBack操作时,都感觉不到服务器是无状态的。
1.2 青春四处绽放—无处不在的ViewState
(1)类似于Dictionary的一种数据结构
如果你曾经使用过Dictionary或者Session的话,那么你应该了解这种Key/Value的数据结构。这里并没有什么高深的理论,ViewState通过String类型的数据作为索引。ViewState对应项中的值可以存储任何类型的值(参数是Object类型),实施上任何类型的值存储到ViewState中都会被装箱为Object类型。
例如,这里我们可以改写上面那个按钮事件中的代码:
protected void btnGetNumber_Click(object sender, EventArgs e)
{
//number++;
//this.lblNumber.Text = number.ToString(); object age = this.ViewState["age"];
if (age == null)
{
age = ;
}
else
{
age = Convert.ToInt32(age) + ;
}
this.ViewState["age"] = age;
this.lblNumber.Text = age.ToString();
}
这里,我们借助ViewState存储了age的状态值,第一次来我给你返回1,后面再来我就加1再返回给你。于是,在上一节我们所提到的那个问题(无法记住上次的number值,每次都返回1)就解决了。
PS:ViewState不能存储所有的数据类型,仅支持以下的这几种:
String、Integer、Boolean、Array、ArrayList、Hashtable以及一些自定义类型
我们都知道,Dictionary和Session都是存储在服务器端的。那么,我们不禁要问,既然我们在服务器端给ViewState增加了一个Key/Value对,并返回给浏览器端,ViewState又是存储在什么位置的呢?
(2)大隐隐于市的“页面级”隐藏字段
跟Session和Dictionary的存储位置不同,ViewState的作用域是页面,也就是说ViewState是存储在浏览器的页面之中的(这里相比Session等,耗费的服务器资源较少,也算是ViewState的优点之一吧),当你关闭某个aspx文件后,那么属于这个aspx的ViewState也就不存在了。或许,这么说来,我们还不是很了解,现在我们来实地看看。
①首先,如果页面上有一个runat="server"的form,当用户请求这个页面时,服务器会自动添加一个_ViewState的隐藏域返回给浏览器。但是,我们发现这个ViewState的value看起来像一串乱码?这是为什么呢?这是因为服务器在向浏览器返回html之前,对ViewState中的内容进行了Base64的加密编码;
②其次,当用户点击页面中的某个按钮提交表单时,浏览器会将这个_VIEWSTATE的隐藏域也一起提交到服务端;服务器端在解析请求时,会将浏览器提交过来的ViewState进行反序列化后填充到ViewState属性中(比如下图中,我们可以通过一个软件将_VIEWSTATE解码得到一个如下图所示的树形结构);再根据业务处理需要,从这个属性中根据索引找到具体的Value值并对其进行操作;操作完成后,再将ViewState进行Base64编码再次返回给浏览器端;
③因此,我们可以得出一个结论:VIEWSTATE适用于同一个页面在不关闭的情况下多次与服务器交互(PostBack)。这里我们也可以通过下图来温习一下ViewState的流程,ViewState存放着“事故现场”,下次可以方便地“还原现场”,将无状态的Http模拟成了有状态的,也让广大的初学者了解不到无状态的这个特性。
1.3 喜欢就会放肆—又爱又恨的ViewState!
事实上,除了我们手动在服务器端向ViewState属性中添加的K/V对数据,我们在aspx.cs代码中为某些服务器控件设置的值(例如:为Repeater设置DataSource中存入的数据集、为Label所设置的Text内容等,但不包括:TextBox、CheckBox、CheckboxList、RadioButtonList)都存入了ViewState中。这样做的话,我们下次再向服务器提交请求时,现有表单中所有的服务器控件状态都会记录在ViewState中提交到服务器,在服务器端可以方便地对这些服务器控件进行有状态的操作并返回,这无疑是让我们欢喜的,因为方便了我们的开发过程,提高了我们的开发效率;
但有人说:“喜欢就会放肆”,ViewState让人又爱又恨啊。例如,在我们使用Repeater的过程中,WebForm会自动将DataSource(数据源,你可以理解为一个集合)存储到ViewState中并返回给浏览器。可以参考下面的例子来实地理解一下:
①含有Repeater的aspx页面:
<form id="form1" runat="server">
<div align="center">
<table class="test">
<tr class="first">
<td>
ID
</td>
<td>
产品名称
</td>
<td>
产品描述
</td>
<td>
删除
</td>
</tr>
<asp:Repeater ID="repeaterProducts" runat="server">
<ItemTemplate>
<tr>
<td>
<%#Eval("Id") %>
</td>
<td>
<%#Eval("Name") %>
</td>
<td>
<%#Eval("Msg") %>
</td>
<td>
<a href='Product.ashx?Action=Delete&Id=<%#Eval("Id") %>'>删除</a>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</table>
</div>
</form>
②后台代码模拟从数据库中取得数据集合并绑定到Repeater中:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this.repeaterProducts.DataSource = this.GetProductList();
this.repeaterProducts.DataBind();
}
} private IList<Product> GetProductList()
{
IList<Product> productList = new List<Product>();
productList.Add(new Product() { Id = , Name = "康师傅方便面", Msg = "就是这个味儿!" });
productList.Add(new Product() { Id = , Name = "统一方便面", Msg = "还是那个味儿!" });
productList.Add(new Product() { Id = , Name = "白象方便面", Msg = "大骨浓汤啊!" });
productList.Add(new Product() { Id = , Name = "日本方便面", Msg = "不只是爱情动作片!" });
productList.Add(new Product() { Id = , Name = "*方便面", Msg = "马英九夸我好吃!" }); return productList;
}
编译生成后,通过查看此页面的html代码,可以明显看到一长串的_VIEWSTATE隐藏域。将此_VIEWSTATE复制到ViewStateDecoder中进行反编码,可以发现它确实存储了Repeater中的数据集合。这里我们不禁要问:展示数据既然已经渲染成了html,为何还要存储在ViewState隐藏域中?如果我们的数据集合是一百行、一千行数据的话,那ViewState隐藏域岂不很大(100k?200k?)?但不幸的是,这是ViewState的设计机制,要想依靠它来保持状态,它就会将服务器控件的状态包括数据集合都存储到其中,在浏览器和服务器之间来回传递保持状态。
这里就涉及到网站的性能问题的探讨了:由于ViewState存储在页本身,因此如果存储较大的值,用户请求显示页面的速度会减慢(这对于互联网系统来说,就是一个噩梦。你会选择一个1秒内响应的网站浏览还是5秒内响应的网站?)。又因为ViewState会随同Form表单一同回传给服务器,如果ViewState很大的话,Http报文也会很大,网站流量消耗也会增大。
那么,有没有一种方法可以让ViewState克制一下呢?别急,请看下面的介绍。
1.4 但爱就是克制—禁用还是不禁用ViewState?
刚刚说到,因为ViewState会一定程度上影响性能,所以我们可以在不需要的时候禁用 ViewState。默认情况下 ViewState 将被启用,并且是由每个控件(而非页面开发人员)来决定存储在 ViewState 中的内容。有时,这一信息对应用程序并没有什么用处(例如上面提到的Repeater的数据集合,已经渲染生成了html显示,还存储了一份副本在ViewState里边)。尽管也没什么害处,但却会明显增加发送到浏览器的页面的大小。因此如果不需要用ViewState,最好还是将它关闭,特别是当 ViewState 很大的时候。当然,ViewState帮我们实现了某些服务器控件状态保持,因此在非必需的情况下,还是可以适度使用的,特别是在开发企业内部信息系统的场景。
那么,怎样来禁用ViewState呢?禁用ViewState又有什么策略呢?下面我们一一来探讨。
①页面级禁用ViewState:在aspx的首部的Page指令集中添加EnableViewState="false",该页面中所有控件的状态都不会存入ViewState的,页面一下就会清爽许多;
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RepeaterViewState.aspx.cs"
Inherits="WebFormDemo.RepeaterViewState" EnableViewState="false" %>
禁用后,再次查看生成的html代码,我们会发现:咦,_VIEWSTATE还在那儿,但是明显比先前的体积小了不少!
再将这个瘦身后的_VIEWSTATE复制到ViewStateDecoder中进行反编码查看,我们会发现,只保存了一个最基本的信息,Repeater的那些数据集合没有存入进去了。
PS:为什么禁用ViewState之后,页面源代码中仍然有_VIEWSTATE的隐藏域?
这是因为就算禁用了viewstate,aspx页面中还是会有一个服务器控件在那里使用,这就是<form runat="server">。这时,如果你将form去掉runat="server",将其变为普通html标签,那么页面就干净了,从此_VIEWSTATE这个隐藏域彻底消失在你的页面中。
②控件级禁用ViewState:在某些场景中,我们只希望禁用某个控件(例如Repater)的ViewState,其他控件仍然通过ViewState保持状态。这时,我们可以给指定的控件设置一个属性EnableViewState="false"即可;
<asp:Repeater ID="repeaterProducts" runat="server" EnableViewState="false">
</asp:Repeater>
③全局级禁用ViewState:园子里的大神老赵(Jeffrey Zhao)曾经说过,“我如果新建一个WebForm项目,做的第一件事情就是去Web.config中将enableViewState设置为false从而将ViewState全局关闭”。那么,我们如果希望将网站中所有页面的ViewState都禁用,总不可能去一个一个页面得修改Page指令吧?ASP.Net为我们提供了一个配置,我们只需要在Web.config的system.web中增加一句配置即可:
<pages enableViewState="false" />
PS:开发中也可以采用大神老赵的做法,先禁用,再选择性启用,毕竟没有非要ViewState才能干成的事儿!
④真正的禁用ViewState:刚刚我们的三种方法实践后,在页面还是出现_VIEWSTATE的隐藏域,尽管它保留了最基本的信息。那么,我们可能会问?怎样才能彻底地真正地禁用ViewState,根本就别给我生成_VIEWSTATE的隐藏域。答案是有的,将<form runat="server"/>的runat="server"去掉,就不会出现了,但那样又会偏离WebForm的开发模式,大部分的服务器控件都无法正常使用,开发效率又会有所损失。
综上所述,在实际开发中应该权衡利弊,特殊情况特殊分析(到底这个场景该不该禁用ViewState),选择是否禁用ViewState,采用何种方式禁用ViewState。对于ViewState的探秘本篇就到此为止,由于我本人理解的也不是很深刻,所以希望各位园友如果有理解,可以回复出来大家探讨共同进步。
二、飞来的利器—UpdatePanel探秘
2.1 从一个简单四则运算计算器说起
假如有以下一个场景,我们要做一个简单的四则计算器。aspx页面代码和后端逻辑代码如下:
(1)aspx页面代码
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>AJAX计算器</title>
</head>
<body>
<form id="form1" runat="server">
<div align="center">
<asp:TextBox ID="txtNumber1" runat="server"></asp:TextBox>
<asp:DropDownList ID="ddlFunc" runat="server">
<asp:ListItem Value="0">+</asp:ListItem>
<asp:ListItem Value="1">-</asp:ListItem>
<asp:ListItem Value="2">*</asp:ListItem>
<asp:ListItem Value="3">/</asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtNumber2" runat="server"></asp:TextBox>
<asp:Button ID="btnGetResult" runat="server" Text="=" Width="50"
onclick="btnGetResult_Click" />
<asp:Label ID="lblResult" runat="server" Text="" Font-Bold="true"></asp:Label>
</div>
</form>
</body>
</html>
(2)后置逻辑代码
public partial class AjaxCalculator : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{ } protected void btnGetResult_Click(object sender, EventArgs e)
{
int number1 = Convert.ToInt32(this.txtNumber1.Text);
int number2 = Convert.ToInt32(this.txtNumber2.Text);
int result = 0;
switch(this.ddlFunc.SelectedValue)
{
case "0":
result = number1 + number2;
break;
case "1":
result = number1 - number2;
break;
case "2":
result = number1 * number2;
break;
case "3":
if(number2 == 0)
{
throw new DivideByZeroException("除数不能为0!");
}
result = number1 / number2;
break;
}
this.lblResult.Text = result.ToString();
}
}
生成后运行该页面,可以达到以下的效果。我们输入两个数字后,选择是加法、减法、还是乘除法后,点击=按钮,即可刷新页面显示运算结果。
在WebForm中,每一次点击runat="server"的按钮都会将调用form.submit将请求提交到服务器,服务器会返回新的页面html进行页面重绘。这是一个整页的刷新操作,不符合AJAX的风格需求。因此,我们想要将其改为AJAX版本的,除了使用基本的XMLHttpRequest外,我们还可以使用基于JQuery的AJAX方案,这些都是轻量级的原生态的AJAX技术方案。但我们伟大的微软(我哭啊,真是为我们考虑啊,连AJAX方案都为我们解决了,而且还提供了AJAX控件供我们使用,我们拖控件的习惯可以用到AJAX方案上了!!!)还为我们提供了一套叫做ASP.Net AJAX的技术方案,通过这套方案,我们可以在ASP.Net很容易地实现AJAX效果,甚至都不需要我们懂JavaScript。因此,也就出现了前些年,很多WebForm开发者陆续使用ASP.Net AJAX Extension进行AJAX开发,纷纷表示:AJAX如此简单,我等岂能不会?但是,虽然它简单易行,由于其性能问题一直被人诟病,而我们这些菜鸟也未能了解其性能问题的原因,本着知其然也知其所以然的目标,现在我们来使用它并剖析它一下。
2.2 天上掉下个林妹妹—使用UpdatePanel控件
不得不说,UpdatePanel真的是天上掉下的林妹妹,一个神奇的控件!有了它,我们可以将页面中需要进行局部刷新的内容放到其ContentTemplate中,一个需要整页刷新的操作便可以成为局部刷新。现在,我们首先来使用其改造刚刚的简单四则计算器页面。
(1)加入UpdatePanel,并将计算器html内容拖入ContentTemplate中
<form id="form1" runat="server">
<div align="center">
<asp:ScriptManager ID="scriptManager" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="updatePanel" runat="server">
<ContentTemplate>
<asp:TextBox ID="txtNumber1" runat="server"></asp:TextBox>
<asp:DropDownList ID="ddlFunc" runat="server">
<asp:ListItem Value="0">+</asp:ListItem>
<asp:ListItem Value="1">-</asp:ListItem>
<asp:ListItem Value="2">*</asp:ListItem>
<asp:ListItem Value="3">/</asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtNumber2" runat="server"></asp:TextBox>
<asp:Button ID="btnGetResult" runat="server" Text="=" Width="50" OnClick="btnGetResult_Click" />
<asp:Label ID="lblResult" runat="server" Text="" Font-Bold="true"></asp:Label>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
(2)运行该页面,通过开发人员工具查看Http请求
通过查看请求报文,我们了解到此次的请求响应不再是返回整页的html内容,而只是我们放在了UpdatePanel里面的html内容,页面也没有再刷新,于是不禁感叹一句:AJAX,So easy!妈妈再也不用担心我的页面了!
2.3 直到看见XmlHttpRequest才是唯一的答案—UpdatePanel原来如此
正当我们沉浸在UpdatePanel为我们提供的神奇的AJAX世界里时,我们不禁对UpdatePanel为我们做了哪些工作产生了兴趣。
(1)首先,我们知道AJAX的核心对象是XmlHttpRequest,那么原生态的AJAX请求的JS方法是如何写的呢?
function ajax(url, onsuccess) {
var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); //创建XMLHTTP对象,考虑兼容性。XHR
xmlhttp.open("POST", url, true); //“准备”向服务器的xx.ashx发出Post请求(GET可能会有缓存问题)。这里还没有发出请求 //AJAX是异步的,并不是等到服务器端返回才继续执行
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4) //readyState == 4 表示服务器返回完成数据了。之前可能会经历2(请求已发送,正在处理中)、3(响应中已有部分数据可用了,但是服务器还没有完成响应的生成)
{
if (xmlhttp.status == 200) //如果Http状态码为200则是成功
{
onsuccess(xmlhttp.responseText);
}
else {
alert("AJAX服务器返回错误!");
}
}
}
//不要以为if (xmlhttp.readyState == 4) {在send之前执行!!!!
xmlhttp.send(); //这时才开始发送请求。并不等于服务器端返回。请求发出去了,我不等!去监听onreadystatechange吧!
}
(2)其次,通过查看运行页面的html,我们可以发现加入UpdatePanel后,我们的html中多了这么几个js引用。
(3)既然我们知道要发AJAX请求,必然会涉及到XmlHttpRequest。那么,我们就在这几个js中取看看是否有涉及到XmlHttpRequest。通过查看,我们找到了这样一个似曾相识的js方法:
function Sys$Net$XMLHttpExecutor$executeRequest() {
/// <summary locid="M:J#Sys.Net.XMLHttpExecutor.executeRequest" />
if (arguments.length !== 0) throw Error.parameterCount();
this._webRequest = this.get_webRequest();
if (this._started) {
throw Error.invalidOperation(String.format(Sys.Res.cannotCallOnceStarted, 'executeRequest'));
}
if (this._webRequest === null) {
throw Error.invalidOperation(Sys.Res.nullWebRequest);
}
var body = this._webRequest.get_body();
var headers = this._webRequest.get_headers();
this._xmlHttpRequest = new XMLHttpRequest();
this._xmlHttpRequest.onreadystatechange = this._onReadyStateChange;
var verb = this._webRequest.get_httpVerb();
this._xmlHttpRequest.open(verb, this._webRequest.getResolvedUrl(), true );
this._xmlHttpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (headers) {
for (var header in headers) {
var val = headers[header];
if (typeof(val) !== "function")
this._xmlHttpRequest.setRequestHeader(header, val);
}
}
if (verb.toLowerCase() === "post") {
if ((headers === null) || !headers['Content-Type']) {
this._xmlHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
}
if (!body) {
body = "";
}
}
var timeout = this._webRequest.get_timeout();
if (timeout > 0) {
this._timer = window.setTimeout(Function.createDelegate(this, this._onTimeout), timeout);
}
this._xmlHttpRequest.send(body);
this._started = true;
}
由以上的方法名我们可以猜到,此方法是一个执行AJAX请求的方法。在此方法中,创建了XmlHttpRequest对象,也使用了open方法指明以GET还是POST方法向服务器哪个处理程序发送请求,并且也为该请求指定了请求成功后需要执行的回调函数方法(onreadystatechange),最后调用send方法正式发送请求
由此,我们可以初步分析出一个结论:UpdatePanel本质还是帮我们封装了以XmlHttpRequest为核心的一系列方法帮我们将CodeBehind中的同步事件变为了异步操作,并通过DOM更新指定的HTML内容,使得我们可以方便地实现AJAX效果。
但是,我们也不由发出感叹:本来可以很简单地使用XmlHttpRequest来实现的东西,为什么使用UpdatePanel会引入这么多js,并且为我们返回的东西还是那么多(比如上面的例子,我只需要的数据是一个结果,却给我返回一部分无用的html,还有一系列的hiddenId之类的数据)。在对性能要求较高的应用场合,如果使用UpdatePanel来实现AJAX会增加服务器的负载,并且会消耗掉不必要的网络流量(比如每次请求都会来回都会发送ViewState里的数据,在性能和数据量上都会造成损失)。园子里的浪子曾经在他的博文《远离UpdatePanel带给我的噩梦》里边写到:“UpdatePanel在页面小的时候还是很好用的,而当页面控件数不断上升的时候,UpdatePanel就开始直线下降,我们现在页面有4,5百个控件,每做一次PostBack需要长达15秒钟之长,实在让人无法忍受。”
那么,有木有方式可以替换UpdatePanel呢?其实答案很简单,那就是使用基于XmlHttpRequest的js方法,再加上一定的js回调函数即可。这就要求我们掌握javascript,不能只做拖UpdatePanel控件的程序员。现在基于js的JQuery库也早已为我们封装了XmlHttpRequest,提供了ajax开发的一系列方法供我们调用,相当于UpdatePanel的“重量级”来说,可谓是轻了不少,是一个“轻量级”的AJAX开发方式。通过借助jQuery Ajax+ashx可以方便地在.Net中进行Ajax开发,并且具有不错的性能,这也是我实习所在的企业中经常用到的方式。
三、学习总结
本篇主要学习了WebForm中的状态保持法宝—ViewState,以及曾经的ASP.Net AJAX方案的利器—UpdatePanel,虽然一直在说这个不好,那个别用。但是,微软之所以为我们提供了这些东西,肯定有它存在的理由,并不一定都是不好的东西。所谓利器在手,没有一点内功心法的人还是使用不好它,无法发挥出其100%的优势。因此,身为.Net学习者的我们,不能满足于微软为我们所提供的便利,要知其然也知其所以然,做一个上进的程序员,加油吧!
校园招聘的大潮就快来临,希望园子里跟我一样即将毕业的菜鸟们能够好好复习基础,在招聘中赢得一份好offer,实现自己的价值!
参考资料
(1)杨中科,《特供2014版ASP.Net教程》,http://net.itcast.cn/subject/tegongnet/index.html
(2)匿名未知,《状态保存机制之ViewState概述及应用》,http://www.jb51.net/article/33686.htm
(3)popping_dancer,《ASP.Net原理篇之ViewState》,http://www.2cto.com/kf/201210/160413.html
(4)玉开,《ASP.Net 4.0新特性-输出更纯净的Html代码》,http://www.cnblogs.com/yukaizhao/archive/2010/05/22/asp-net-new-feature-pure-html.html
(5)Infinities Loop,《ASP.Net ViewState详解》,http://www.cnblogs.com/wwan/archive/2010/11/18/1880357.html
(6)LifelongLearning,《ASP.Net 4.0中ViewState使用简介》,http://www.csharpwin.com/dotnetspace/13220r6527.shtml
(7)*飞,《禁用VIEWSTATE之后(一)》,http://www.cnblogs.com/freeflying/archive/2009/12/28/1634229.html
(8)MSDN,《了解使用ASP.NET AJAX进行局部页面刷新》,http://msdn.microsoft.com/zh-cn/dd361854.aspx
(9)xiaomin,《UpdatePanel工作原理》,http://www.cnblogs.com/xiaomin/archive/2011/11/01/2232215.html
(10)浪子,《远离UpdatePanel给我的噩梦》,http://www.cnblogs.com/walkingboy/archive/2007/01/09/615691.html