ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘

开篇:经历了上一篇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类型。

  例如,这里我们可以改写上面那个按钮事件中的代码:

 1 protected void btnGetNumber_Click(object sender, EventArgs e)
 2 {
 3     //number++;
 4     //this.lblNumber.Text = number.ToString();
 5 
 6     object age = this.ViewState["age"];
 7     if (age == null)
 8     {
 9         age = 1;
10     }
11     else
12     {
13         age = Convert.ToInt32(age) + 1;
14     }
15     this.ViewState["age"] = age;
16     this.lblNumber.Text = age.ToString();
17 }
View Code

   这里,我们借助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页面:

 1     <form id="form1" runat="server">
 2         <div align="center">
 3             <table class="test">
 4                 <tr class="first">
 5                     <td>
 6                         ID
 7                     </td>
 8                     <td>
 9                         产品名称
10                     </td>
11                     <td>
12                         产品描述
13                     </td>
14                     <td>
15                         删除
16                     </td>
17                 </tr>
18                 <asp:Repeater ID="repeaterProducts" runat="server">
19                     <ItemTemplate>
20                         <tr>
21                             <td>
22                                 <%#Eval("Id") %>
23                             </td>
24                             <td>
25                                 <%#Eval("Name") %>
26                             </td>
27                             <td>
28                                 <%#Eval("Msg") %>
29                             </td>
30                             <td>
31                                 <a href='Product.ashx?Action=Delete&Id=<%#Eval("Id") %>'>删除</a>
32                             </td>
33                         </tr>
34                     </ItemTemplate>
35                 </asp:Repeater>
36             </table>
37         </div>
38     </form>
View Code

  ②后台代码模拟从数据库中取得数据集合并绑定到Repeater中:

 1 protected void Page_Load(object sender, EventArgs e)
 2 {
 3     if (!IsPostBack)
 4     {
 5         this.repeaterProducts.DataSource = this.GetProductList();
 6         this.repeaterProducts.DataBind();
 7     }
 8 }
 9 
10 private IList<Product> GetProductList()
11 {
12     IList<Product> productList = new List<Product>();
13     productList.Add(new Product() { Id = 1, Name = "康师傅方便面", Msg = "就是这个味儿!" });
14     productList.Add(new Product() { Id = 2, Name = "统一方便面", Msg = "还是那个味儿!" });
15     productList.Add(new Product() { Id = 3, Name = "白象方便面", Msg = "大骨浓汤啊!" });
16     productList.Add(new Product() { Id = 4, Name = "日本方便面", Msg = "不只是爱情动作片!" });
17     productList.Add(new Product() { Id = 5, Name = "台湾方便面", Msg = "马英九夸我好吃!" });
18 
19     return productList;
20 }    
View Code

  编译生成后,通过查看此页面的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>
View Code

  (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();
        }
}
View Code

  生成后运行该页面,可以达到以下的效果。我们输入两个数字后,选择是加法、减法、还是乘除法后,点击=按钮,即可刷新页面显示运算结果。

  在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

 

posted @ 2014-08-11 01:48 Edison Chou 阅读(...) 评论(...) 编辑 收藏