Tuesday, February 28, 2006

 

Some websites for Ajax

Atlas主页
http://atlas.asp.net
http://weblogs.asp.net/atlas/

Atlas核心人物的Blog:
http://blogs.msdn.com/brada/
http://weblogs.asp.net/scottgu/
http://www.nikhilk.net/
http://weblogs.asp.net/bleroy/
http://blogs.msdn.com/jhawk/

观众们的blog
http://dflying.cnblogs.com/

Saturday, February 11, 2006

 

Some notes on Ajax.Pro

1.
By sunwaywei
From http://sunwaywei.cnblogs.com/archive/2006/04/15/375928.html

AjaxPro 内部机制探讨
  应当承认我这人实在算不上弄潮儿,Ajax 早已流行得一塌糊涂,我却始终没有来研究一下这个东东。上次做网站的时候,BOSS 就跟我讲过,可以参考一下 Ajax 的技术,我嘴上答应,心里却不是十分的在乎。究其原因,一来是我这人比较固步自封,二来起初确实也没太相信 Ajax 真有 BOSS 说的那么神奇。

  转变是从昨天天始的,这一周在公司主要精力都是在用 C++ 写 framework,不得不承认它比较辛苦,细枝末节之处非常之烦,昨天下午呆着呆着就不想干活了,就开始四处游荡,正好看到我们自己也有项目已经成功应用了 Ajax,于是也就想看一看,无奈那帮家伙的开发文档是出奇的少,只好在网上找找资料,自己研究研究吧。

  作为一个技术人员,我看到一项新技术,总是喜欢琢磨琢磨它内部是如何实现的。在对 Ajax 有了初步认识以后,自然想看看其内部机制,但是令我失望的是,至少介绍 Ajax 内部实现的文章少之又少,好容易找到一篇,却也只是简单列了列一些 javascript 代码,并且没什么解释,颇为郁闷。想想求人不如求己,况且自己研究的或许印象更深一些。于是找到了一个 AjaxPro,下来琢磨琢磨,只是对于 JavaScript 我实在知之甚少,不明白之处依然很多,不过还是想写出来,抛砖引玉,望高人们不吝指教。

  一、使用的例子

  本文使用的例子很简单,一个文本框,在其中敲入文字之后,下方就显示该文字并加上一个“(Hello from server)”。源码如下(有删节):

1%@ Page language="c#" ClassName="KeyPressDemo" Inherits="System.Web.UI.Page" %
2script runat="server" language="c#"
3
4private void Page_Load(object sender, EventArgs e)
5{
6 AjaxPro.Utility.RegisterTypeForAjax(typeof(KeyPressDemo));
7}
8
9[AjaxPro.AjaxMethod]
10public string EchoInput(string s)
11{
12 return s += " (Hello from server)";
13}
14
15/script
16
17form id="Form1" method="post" runat="server"/form
18
19div class="content"
20h1KeyPressDemo Examples/h1
21pPress any key in the textbox and see the echo in the DIV element on the right side./p
22input type="text" id="myinput" onkeyup="doTest1();"/ div id="mydisplay"---- empty ----/div
23piNote, that I do not update the display if a request is running currently./i/p
24/div
25
26script type="text/javascript" defer="defer"
27
28var timer = null;
29
30function doTest1() {
31 if(timer != null) {
32 clearTimeout(timer);
33 }
34 timer = setTimeout(doTest1_next, 100);
35}
36
37function doTest1_next() {
38 var ele = document.getElementById("myinput");
39 ASP.KeyPressDemo.EchoInput(ele.value, doTest1_callback);
40}
41
42function doTest1_callback(res) {
43 var ele = document.getElementById("mydisplay");
44 ele.innerHTML = res.value;
45}
46
47/script
  选用这个例子,是因为它比较简单,没有相关的 C# 文件。首先看看第17行,咦?怎么这个 form 里啥都没有?既然什么都没有?删掉它行不行?不行,绝对不行!看看网页打开后,这一行被扩展成了什么?

1form name="Form1" method="post" action="keypress.aspx" id="Form1"
2div
3script type="text/javascript" src="/ajaxdemo/ajaxpro/core.ashx"/script
4script type="text/javascript" src="/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx"/script
5/form
6
  请注意这里链入的两个 javascript 文件,它们是 Ajax 得以运行的基础!删掉 form 那一行,它们就不会出现,自然不行了。这两行是如何产生的?那就是页面代码的第4至7行的 PageLoad 函数的功劳了。

  好,那这两个 javascript 文件我们能看到不?看上去它们是服务端的,并且事实上是服务端动态生成的。不过稍有些了解浏览器工作原理的人就会知道,到 Local Settings 下的 Temporary Internet Files 目录下去找,肯定是有的,因为浏览器下载页面的时候会把与页面相关的文件都下过来。

  二、Ajax ClientScript 的执行总体流程

  好,有了源页面代码,又有了两个 ClientScript 文件,我们就可以分析客户端的执行流程了。以下是我画的一张简单的流程图:



  我们一个一个地来分析。

  三、HTML页面做了什么?

  第一步,当我们在 TextBox 里输入字符后,将会触发 onkeyup 事件。它要执行 doTest1 方法。见页面代码里的第22行。

  第二步,doTest1 方法使用 setTimeout 函数,设定了 100 毫秒后,执行 doTest1_next 方法。见页面代码里的第34行。

  第三步,doTest1_next 方法调用了 ASP.KeyPressDemo.EchoInput 方法,它带有两个参数,第一个是我们在文本框中输入的值,当然是个字符串类型的了;第二个则是一个 callback 函数,请留心这个函数,它将于整个流程的最后执行。

  好,我们知道页面的客户端无外乎就是 HTML 和 JavaScript,虽然 ASP.KeyPressDemo.EchoInput 方法酷似页面里我们自己用 C# 写的函数,但可以肯定的是它绝对是用 JavaScript 实现的。在哪儿呢?嗯,在我们从 Temporary Internet Files 目录下找到的 ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 里。

  四、ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 的实现

  这个文件很小,以下是它的全部源码:

1addNamespace("ASP");
2ASP.KeyPressDemo_class = Class.create();
3ASP.KeyPressDemo_class.prototype = (new AjaxPro.Request()).extend({
4 EchoInput: function(s, callback) {
5 return this.invoke("EchoInput", {"s":s}, callback);
6 },
7 initialize: function() {
8 this.url = "/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx";
9 }
10})
11ASP.KeyPressDemo = new ASP.KeyPressDemo_class();
12
  啊哈,这下我们知道了,ASP.KeyPressDemo 其实是在这里用 JavaScript 定义的 ASP.KeyPressDemo_class 类的实例,EchoInput 则是它的一个方法。注意一下每3行,我们看到这个类是从 AjaxPro.Request 类继承的。什么什么?继承?有没有搞错?JavaScript 什么时候开始面向对象了而不是基于对象了?先摆下这个疑问,我们继续往下看。

  EchoInput 方法的实现很简单,就是调用了一个 Invoke 方法。嗯,这个方法想必是从 AjaxPro.Request 类“继承”下来的。那它定义在哪儿?是了,还有一个 core.ashx 呢,它才是真正客户端实现 Ajax 技术的主角!这个文件太大,我们还是依照函数调用顺序慢慢来拆解罢。

  五、Invoke 函数

  Invoke 函数是核心所在,前面我画的流程图中已经简单地描述了它的主要流程。不过这个函数太重要了,这里还是列出它的全部源码:

1AjaxPro.Request = Class.create();
2AjaxPro.Request.prototype = (new AjaxPro.Base()).extend({
3 invoke: function(method, data, callback) {
4 var async = typeof callback == "function" && callback != AjaxPro.noOperation;
5 var json = AjaxPro.toJSON(data) + "\r\n";
6
7 if(AjaxPro.cryptProvider != null)
8 json = AjaxPro.cryptProvider.encrypt(json);
9
10 this.callback = callback;
11
12 if(async) {
13 this.xmlHttp.onreadystatechange = this.doStateChange.bind(this);
14 if(typeof this.onLoading == "function") this.onLoading(true);
15 }
16
17 this.xmlHttp.open("POST", this.url, async);
18 this.xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
19 this.xmlHttp.setRequestHeader("Content-Length", json.length);
20 this.xmlHttp.setRequestHeader("Ajax-method", method);
21
22 if(AjaxPro.token != null && AjaxPro.token.length 0)
23 this.xmlHttp.setRequestHeader("Ajax-token", AjaxPro.token);
24
25 if(MS.Browser.isIE)
26 this.xmlHttp.setRequestHeader("Accept-Encoding", "gzip, deflate");
27 else
28 this.xmlHttp.setRequestHeader("Connection", "close"); // Mozilla Bug #246651
29
30 if(this.onTimeout != null && typeof this.onTimeout == "function")
31 this.timeoutTimer = setTimeout(this.timeout.bind(this), this.timeoutPeriod);
32
33 this.xmlHttp.send(json);
34
35 json = null;
36 data = null;
37 delete json;
38 delete data;
39
40 if(!async) {
41 return this.createResponse();
42 }
43
44 return true;
45 }
46});
47

  嗯,相当复杂啊。我们慢慢地看。

  AjaxPro.Request 类当然不是只有 Invoke 一个函数,这里省去了其它函数。嗯,我们看到,AjaxPro.Request 也是从 AjaxPro.Base “继承”下来的。

  第4行的 async,字面上理解就是指异步,这一行什么意思?嗯,如果传进来的 callback 类型是函数,并且不是无操作,那就认为是异步的。

  第5行的 json,它可是相当重要啊。这里调用了 AjaxPro.toJSON 方法把传进来的数据进行了某种编码,本例中这个数据当然就是从 doTest1_next 一路传进来的 TextBox 里我们输入的字符串值了,这个函数的实现,本文也不再列出,可以参见 core.ashx 文件。

  接下来第7到8行,如果提供了加密,那么就对 json 进行加密。这个好理解。

  第12到15行,如果是异步的,那么这里将 doStateChange 函数绑定到 onreadystatechange 事件上去。嗯,这里的绑定其实也是在 core.ashx 文件里声明的一个方法,本文不再阐述它的实现了,大家有兴趣,可以自己去看。绑定完成后,当服务端完成操作后,doStateChange 函数会被调用,这时可以进行更改页面的工作。此外,这里还检测了一下 onLoading 事件。

  第17行到第33行可谓核心代码,我们知道 Ajax 就是使用的 XMLHttpRequest 来完成无刷新页面的。这里我们可看到 this.xmlHttp 被用来进行了请求封装。其中值得我们注意的,Content-Length 使用的 json.length,Ajax-method 则使用的就是传进来的 AjaxMethod 方法名称,本例中为 EchoInput。第30、31行设置了超时处理,当然了,页面不能死等嘛。第33行则将 json 发送到服务端。

  接下来的第41行,我们看到如果不是异步操作的话,此处将直接调用 createResponse 函数获得响应。那如果是异步操作呢?记得我们设置了 doStateChange 吧?异步的返回处理就是它的事了。createResponse 函数后面再介绍。

  六、解释“继承”

  前面我们好几次看到貌似继承。当然它们都仅仅是貌似而已。看看以下 core.ashx 中的代码就明白了:

1Object.extend = function(destination, source) {
2 for(property in source) {
3 destination[property] = source[property];
4 }
5 return destination;
6}
7
  哈哈,所谓的“继承”,其实只是个属性拷贝而已。

  七、this.xmlHttp 从何而来?

  前面我们看到了 this.xmlHttp 大显神威。那么它是哪儿来的?看看 AjaxPro.Request 类的 initialize 函数吧(有删节):

1initialize: function(url) {
2 this.xmlHttp = new XMLHttpRequest();
3}
4
  是了,xmlHttp 只是 XMLHttpRequest 的一个实例。那么 XMLHttpRequest 的定义呢?

1var lastclsid = null;
2if(!window.XMLHttpRequest) {
3
4 function getXmlHttp(clsid) {
5 var xmlHttp = null;
6 try {
7 xmlHttp = new ActiveXObject(clsid);
8 lastclsid = clsid;
9 return xmlHttp;
10 } catch(ex) {}
11 }
12
13 window.XMLHttpRequest = function() {
14 if(lastclsid != null) {
15 return getXmlHttp(lastclsid);
16 }
17
18 var xmlHttp = null;
19 var clsids = ["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0",
"Msxml2.XMLHTTP.2.6","Microsoft.XMLHTTP.1.0","Microsoft.XMLHTTP.1","Microsoft.XMLHTTP"];
20
21 for(var i=0; iclsids.length && xmlHttp == null; i++) {
22 xmlHttp = getXmlHttp(clsids[i]);
23 }
24
25 if(xmlHttp == null) {
26 return new IFrameXmlHttp();
27 }
28
29 return xmlHttp;
30 }
31}
32
  哦,原来是在这里真正创建的。说到底还是一个 ActiveXObject 啊。关于这个本文也不再多提。不过代码中还需要注意的一点是,
如果把第19行列出的一大堆clsids 都处理过了还没有得到对象怎么办?注意到第26行 new 了一个 IFrameXmlHttp。

  IFrameHttp 是在 core.ashx 中定义的,它基本上完全模拟了 ActiveXObject 对象的功能。想研究研究的,自己看源码吧。篇幅所限,这里不多讲啦。

  八、doStateChange 函数

  嗯,前面已经提过,异步的话 doStateChange 函数将会在服务端返回后执行,看看它的源码呢:

1doStateChange: function() {
2 if(this.onStateChanged != null && typeof this.onStateChanged == "function")
3 try{ this.onStateChanged(this.xmlHttp.readyState); }catch(e){}
4
5 if(this.xmlHttp.readyState != 4)
6 return;
7
8 if(this.xmlHttp.status == 200) {
9 if(this.timeoutTimer != null) clearTimeout(this.timeoutTimer);
10 if(typeof this.onLoading == "function") this.onLoading(false);
11
12 this.xmlHttp.onreadystatechange = AjaxPro.noOperation;
13
14 this.callback(this.createResponse());
15 this.callback = null;
16
17 this.xmlHttp.abort();
18 }
19},
20
  如果 status 是 200,也就是 OK,那么清除掉超时处理函数,处理 onLoading 事件,最后使用 callback 调用 createResponse 函数。还记得如果不是异步的话,createResponse 将会直接调用而不是通过 doStateChange 吧。

  九、createResponse 函数

1createResponse: function() {
2 var r = new Object();
3 r.error = null;
4 r.value = null;
5
6 var responseText = new String(this.xmlHttp.responseText);
7
8 if(AjaxPro.cryptProvider != null && typeof AjaxPro.cryptProvider == "function")
9 responseText = AjaxPro.cryptProvider.decrypt(responseText);
10
11 eval("r.value = " + responseText + ";");
12
13 if(r.error != null && this.onError != null && typeof this.onError == "function")
14 try{ this.onError(r.error); }catch(e){}
15
16 responseText = null;
17
18 return r;
19}
  如果前面的 json 也就是 Request 是加过密的,这里就需要对 responseText 进行解密。完了之后得到 r.value,r 将会被返回并提供给 callback 函数。本例中将最终传回 doTest1_callback,r 被传入它的 res 参数。最后更新文本框下的字符串,整个 Ajax ClientScript 的流程就差不多是完成了。

  十、简单总结一下

  呼,长出一口气。总算可以告一段落了,AjaxPro 服务端的拆解过段时间再说吧。

  在分析 ClientScript 端的时候真是大有感触,JavaScript 其实远比人们想象的强大和管用。其实我同大多数人一样,起初也对它很不感冒,但是之前曾有两件事让我改变了观念。其一是阅读了黄忠成的《深入剖析 ASP.NET 组件设计》,才发现原来许多强大炫目的 ASP.NET 的控件,其实都是用的 JavaScript 实现。其二是在研究国外某文档浏览器实现的时候,发现人家使用 JavaScript 在 IE 下相当完美地实现了强大灵活有如桌面程序的界面和功能,真是吃惊不小。当时就发现了自己对 JavaScript 的了解实在是严重汗颜,惭愧无地。无奈平时没有多少时间去学习提高自己,只能偶尔抽抽空余时间了解了解,充充电吧。

  相信 JavaScript 之类的脚本必将在未来的 Web 应用中大展身手。

Monday, February 06, 2006

 

Some notes on Ajax and its applications

I.
From http://frankboy.cnblogs.com/archive/2005/12/27/306146.html
http://frankboy.cnblogs.com/archive/2005/12/27/306147.html

由于在研究web即时通技术(TRACQ,网眼,网站商务通),所以对Ajax感兴趣起来,最近好像Ajax很火,从搜索引擎的结果可见一斑。baidu找到181,000篇相关文档,google找到了6,790,000篇。我觉得,在没有提出Ajax这个概念以前,肯定有人已经在利用这些技术做事情了,只是在最近才被炒的热闹起来。我先把网上找的资料整理在这里。

  1、定义

  Ajax最早是由Jesse James Garrett提出来的,原文:Ajax: A New Approach to Web Applications
  网上公认的定义:Ajax是Asynchronous JavaScript XML的缩写,它也不是单一技术,而是几种技术的强有力组合。它包括
  使用XHTML和CSS标准化呈现;
  使用DOM实现动态显示和交互;
  使用XML和XSLT进行数据交换与处理;
  使用XMLHttpRequest进行异步数据读取;
  最后用JavaScript绑定和处理所有数据;

  2、为什么用Ajax

  就我自己而言,我是因为想让web即时通客户端不受不断刷新页面的烦扰。
  1、通过适当的Ajax应用达到更好的用户体验;2、把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理,减轻服务器和带宽的负担,从而达到节约ISP的空间及带宽租用成本的目的。

  3、已有的Ajax应用

msn space
Orkut
Gmail
Google Groups
Google Suggest
Google Maps


  4、Ajax的相关文章和资源

  Ajax: A New Approach to Web Applications 中文译版
  AJAX:Getting Started 中文版
  Ajax内部交流文档
  Ajax程序设计入门
  循序渐进学习 Ajax 的途径
  java视线论坛Ajax版
  http://www.ajaxmatters.com/

  5、学习ajax要看的书 原文

  1)、XHTML 教程(XHTML)
  2)、JavaScript 权威指南第四版(JavaScript: The Definitive Guide)
  3)、XML 高级编程(Professional XML)
  4)、网站重构(Designing with Web Standards)

一、使用Ajax的主要原因
  1、通过适当的Ajax应用达到更好的用户体验;
  2、把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理,减轻服务器和带宽的负担,从而达到节约ISP的空间及带宽租用成本的目的。

二、引用
Ajax这个概念的最早提出者Jesse James Garrett认为:
  Ajax是Asynchronous JavaScript and XML的缩写。
  Ajax并不是一门新的语言或技术,它实际上是几项技术按一定的方式组合在一在同共的协作中发挥各自的作用,它包括
  使用XHTML和CSS标准化呈现;
  使用DOM实现动态显示和交互;
  使用XML和XSLT进行数据交换与处理;
  使用XMLHttpRequest进行异步数据读取;
  最后用JavaScript绑定和处理所有数据;
  Ajax的工作原理相当于在用户和服务器之间加了—个中间层,使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像—些数据验证和数据处理等都交给Ajax引擎自己来做,只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。


图2-1


图2-2
三、概述
  虽然Garrent列出了7条Ajax的构成技术,但个人认为,所谓的Ajax其核心只有 JavaScript、XMLHTTPRequest和DOM,如果所用数据格式为XML的话,还可以再加上XML这一项(Ajax从服务器端返回的数据可以是XML格式,也可以是文本等其他格式)。
  在旧的交互方式中,由用户触发一个HTTP请求到服务器,服务器对其进行处理后再返回一个新的HTHL页到客户端,每当服务器处理客户端提交的请求时,客户都只能空闲等待,并且哪怕只是一次很小的交互、只需从服务器端得到很简单的一个数据,都要返回一个完整的HTML页,而用户每次都要浪费时间和带宽去重新读取整个页面。
  而使用Ajax后用户从感觉上几乎所有的操作都会很快响应没有页面重载(白屏)的等待。
  1、XMLHTTPRequest
  Ajax的一个最大的特点是无需刷新页面便可向服务器传输或读写数据(又称无刷新更新页面),这一特点主要得益于XMLHTTP组件 XMLHTTPRequest对象。这样就可以向再发桌面应用程序只同服务器进行数据层面的交换,而不用每次都刷新界面也不用每次将数据处理的工作提交给服务器来做,这样即减轻了服务器的负担又加快了响应速度、缩短了用户等候时间。
  最早应用XMLHTTP的是微软,IE(IE5以上)通过允许开发人员在Web页面内部使用XMLHTTP ActiveX组件扩展自身的功能,开发人员可以不用从当前的Web页面导航而直接传输数据到服务器上或者从服务器取数据。这个功能是很重要的,因为它帮助减少了无状态连接的痛苦,它还可以排除下载冗余HTML的需要,从而提高进程的速度。Mozilla(Mozilla1.0以上及NetScape7以上)做出的回应是创建它自己的继承XML代理类:XMLHttpRequest类。Konqueror (和Safari v1.2,同样也是基于KHTML的浏览器)也支持XMLHttpRequest对象,而Opera也将在其v7.6x+以后的版本中支持 XMLHttpRequest对象。对于大多数情况,XMLHttpRequest对象和XMLHTTP组件很相似,方法和属性也类似,只是有一小部分属性不支持。
XMLHttpRequest的应用:

XMLHttpRequest对象在JS中的应用
var xmlhttp = new XMLHttpRequest();
微软的XMLHTTP组件在JS中的应用
var xmlhttp = new ActiveXObject(Microsoft.XMLHTTP);
var xmlhttp = new ActiveXObject(Msxml2.XMLHTTP);

XMLHttpRequest 对象方法

/**
* Cross-browser XMLHttpRequest instantiation.
*/

if (typeof XMLHttpRequest == 'undefined') {
XMLHttpRequest = function () {
var msxmls = ['MSXML3', 'MSXML2', 'Microsoft']
for (var i=0; i < msxmls.length; i++) {
try {
return new ActiveXObject(msxmls[i]+'.XMLHTTP')
} catch (e) { }
}
throw new Error("No XML component installed!")
}
}
function createXMLHttpRequest() {
try {
// Attempt to create it "the Mozilla way"
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
// Guess not - now the IE way
if (window.ActiveXObject) {
return new ActiveXObject(getXMLPrefix() + ".XmlHttp");
}
}
catch (ex) {}
return false;
};


XMLHttpRequest 对象方法 方法 描述
abort() 停止当前请求
getAllResponseHeaders() 作为字符串返问完整的headers
getResponseHeader("headerLabel") 作为字符串返问单个的header标签
open("method","URL"[,asyncFlag[,"userName"[, "password"]]]) 设置未决的请求的目标 URL, 方法, 和其他参数
send(content) 发送请求
setRequestHeader("label", "value") 设置header并和请求一起发送

XMLHttpRequest 对象属性 属性 描述
onreadystatechange 状态改变的事件触发器
readyState 对象状态(integer):
0 = 未初始化
1 = 读取中
2 = 已读取
3 = 交互中
4 = 完成
responseText 服务器进程返回数据的文本版本
responseXML 服务器进程返回数据的兼容DOM的XML文档对象
status 服务器返回的状态码, 如:404 = "文件末找到" 、200 ="成功"
statusText 服务器返回的状态文本信息

  2、JavaScript
  JavaScript是一在浏览器中大量使用的编程语言,,他以前一直被贬低为一门糟糕的语言(他确实在使用上比较枯燥),以在常被用来作一些用来炫耀的小玩意和恶作剧或是单调琐碎的表单验证。但事实是,他是一门真正的编程语言,有着自已的标准并在各种浏览器中被广泛支持。
  3、DOM
  Document Object Model。
  DOM是给 HTML 和 XML 文件使用的一组 API。它提供了文件的结构表述,让你可以改变其中的內容及可见物。其本质是建立网页与 Script 或程序语言沟通的桥梁。
  所有WEB开发人员可操作及建立文件的属性、方法及事件都以对象来展现(例如,document 就代表“文件本身“这个对像,table 对象则代表 HTML 的表格对象等等)。这些对象可以由当今大多数的浏览器以 Script 来取用。
  一个用HTML或XHTML构建的网页也可以看作是一组结构化的数据,这些数据被封在DOM(Document Object Model)中,DOM提供了网页中各个对象的读写的支持。
  4、XML
  可扩展的标记语言(Extensible Markup Language)具有一种开放的、可扩展的、可自描述的语言结构,它已经成为网上数据和文档传输的标准。它是用来描述数据结构的一种语言,就正如他的名字一样。他使对某些结构化数据的定义更加容易,并且可以通过他和其他应用程序交换数据。
  5、综合
  Jesse James Garrett提到的Ajax引擎,实际上是一个比较复杂的JavaScript应用程序,用来处理用户请求,读写服务器和更改DOM内容。
  JavaScript的Ajax引擎读取信息,并且互动地重写DOM,这使网页能无缝化重构,也就是在页面已经下载完毕后改变页面内容,这是我们一直在通过JavaScript和DOM在广泛使用的方法,但要使网页真正动态起来,不仅要内部的互动,还需要从外部获取数据,在以前,我们是让用户来输入数据并通过DOM来改变网页内容的,但现在,XMLHTTPRequest,可以让我们在不重载页面的情况下读写服务器上的数据,使用户的输入达到最少。
  基于XML的网络通讯也并不是新事物,实际上FLASH和JAVA Applet都有不错的表现,现在这种富交互在网页上也可用了,基于标准化的并被广泛支持和技术,并且不需要插件或下载小程序。
  Ajax是传统WEB应用程序的一个转变。以前是服务器每次生成HTML页面并返回给客户端(浏览器)。在大多数网站中,很多页面中至少90%都是一样的,比如:结构、格式、页头、页尾、广告等,所不同的只是一小部分的内容,但每次服务器都会生成所有的页面再返回给客户端,这无形之中是一种浪费,不管是对于用户的时间、带宽、CPU耗用,还是对于ISP的高价租用的带宽和空间来说。如果按一页来算,只能几K或是几十K可能并不起眼,但像SINA每天要生成几百万个页面的大ISP来说,可以说是损失巨大的。而AJAX可以所为客户端和服务器的中间层,来处理客户端的请求,并根据需要向服务器端发送请求,用什么就取什么、用多少就取多少,就不会有数据的冗余和浪费,减少了数据下载总量,而且更新页面时不用重载全部内容,只更新需要更新的那部分即可,相对于纯后台处理并重载的方式缩短了用户等待时间,也把对资源的浪费降到最低,基于标准化的并被广泛支持和技术,并且不需要插件或下载小程序,所以Ajax对于用户和ISP来说是双盈的。
  Ajax使WEB中的界面与应用分离(也可以说是数据与呈现分离),而在以前两者是没有清晰的界限的,数据与呈现分离的分离,有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。也可以把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理。

四、应用
  Ajax理念的出现,揭开了无刷新更新页面时代的序幕,并有代替传统web开发中采用form(表单)递交方式更新web页面的趋势,可以算是一个里程碑。但Ajax都不是适用于所有地方的,它的适用范围是由它的特性所决定的。
  举个应用的例子,是关于级联菜单方面的Ajax应用。
  我们以前的对级联菜单的处理是这样的:
  为了避免每次对菜单的操作引起的重载页面,不采用每次调用后台的方式,而是一次性将级联菜单的所有数据全部读取出来并写入数组,然后根据用户的操作用 JavaScript来控制它的子集项目的呈现,这样虽然解决了操作响应速度、不重载页面以及避免向服务器频繁发送请求的问题,但是如果用户不对菜单进行操作或只对菜单中的一部分进行操作的话,那读取的数据中的一部分就会成为冗余数据而浪费用户的资源,特别是在菜单结构复杂、数据量大的情况下(比如菜单有很多级、每一级菜又有上百个项目),这种弊端就更为突出。
  如果在此案中应用Ajax后,结果就会有所改观:
  在初始化页面时我们只读出它的第一级的所有数据并显示,在用户操作一级菜单其中一项时,会通过Ajax向后台请求当前一级项目所属的二级子菜单的所有数据,如果再继续请求已经呈现的二级菜单中的一项时,再向后面请求所操作二级菜单项对应的所有三级菜单的所有数据,以此类推……这样,用什么就取什么、用多少就取多少,就不会有数据的冗余和浪费,减少了数据下载总量,而且更新页面时不用重载全部内容,只更新需要更新的那部分即可,相对于后台处理并重载的方式缩短了用户等待时间,也把对资源的浪费降到最低。
  此外,Ajax由于可以调用外部数据,也可以实现数据聚合的功能(当然要有相应授权),比如微软刚刚在3月15日发布的在线RSS阅读器BETA版;还可以利于一些开放的数据,开发自已的一些应用程序,比如用Amazon的数据作的一些新颖的图书搜索应用。
  总之,Ajax适用于交互较多,频繁读数据,数据分类良好的WEB应用。

五、Ajax的优势
  1、减轻服务器的负担。因为Ajax的根本理念是“按需取数据”,所以最大可能在减少了冗余请求和响影对服务器造成的负担;
  2、无刷新更新页面,减少用户实际和心理等待时间;
  首先,“按需取数据”的模式减少了数据的实际读取量,打个很形象的比方,如果说重载的方式是从一个终点回到原点再到另一个终点的话,那么Ajax就是以一个终点为基点到达另一个终点;


图5-1


图5-2
  其次,即使要读取比较大的数据,也不用像RELOAD一样出现白屏的情况,由于Ajax是用XMLHTTP发送请求得到服务端应答数据,在不重新载入整个页面的情况下用Javascript操作DOM最终更新页面的,所以在读取数据的过程中,用户所面对的也不是白屏,而是原来的页面状态(或者可以加一个LOADING的提示框让用户了解数据读取的状态),只有当接收到全部数据后才更新相应部分的内容,而这种更新也是瞬间的,用户几乎感觉不到。总之用户是很敏感的,他们能感觉到你对他们的体贴,虽然不太可能立竿见影的效果,但会在用户的心中一点一滴的积累他们对网站的依赖。
  3、更好的用户体验;
  4、也可以把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理,减轻服务器和带宽的负担,节约空间和带宽租用成本;
  5、Ajax由于可以调用外部数据;
  6、基于标准化的并被广泛支持和技术,并且不需要插件或下载小程序;
  7、Ajax使WEB中的界面与应用分离(也可以说是数据与呈现分离);
  8、对于用户和ISP来说是双盈的。

六、Ajax的问题
  1、一些手持设备(如手机、PDA等)现在还不能很好的支持Ajax;
  2、用JavaScript作的Ajax引擎,JavaScript的兼容性和DeBug都是让人头痛的事;
  3、Ajax的无刷新重载,由于页面的变化没有刷新重载那么明显,所以容易给用户带来困扰――用户不太清楚现在的数据是新的还是已经更新过的;现有的解决有:在相关位置提示、数据更新的区域设计得比较明显、数据更新后给用户提示等;
  4、对流媒体的支持没有FLASH、Java Applet好;

七、结束语
  更好的Ajax应用,需要更多的客户端的开发,和对当前的WEB应用理念的思考,而且良好的用户体验,来源于为处处用户考虑的理念,而不单纯是某种技术。

II.

Sunday, February 05, 2006

 

Atlas 实现机制浅析

By Flier Lu
From http://flier.cnblogs.com/archive/2006/02/17/332164.html
http://flier.cnblogs.com/archive/2006/02/18/332876.html
http://flier.cnblogs.com/archive/2006/02/19/333695.html

[0] 概述

上周 MS 发布了面向 ASP.NET 的 AJAX 框架 —— Atlas 最新 CTP 2006.1 预览版, ScottGu 在其 blog 上做了较为详细的介绍。

New Atlas Build Available for Download with ASP.NET 2.0

更详细的更新说明,可以参考 nikhilk 的一篇 blog

Atlas M1 Refresh - Some More Goodies

关于 Altas 的概况介绍和简单实例,可以参考 nikhilk 在 PDC05 上的一个讲义和实例

Atlas Presentation Slides and Demos

详细的介绍和使用方法,可以参考 atlas.asp.net 网站,以及相关的 quickstart 教程,这里就不再罗嗦了。

有兴趣做试验的朋友,可以下载 ScottGu 以前写的一个例子,后面的很多分析也将在这个例子的基础上进行。

Making a List, Checking it Twice (Cool Ajax Sample App with ASP.NET 2.0 and Atlas)

与 .NET 和 Java 平台下其它 AJAX 框架相比,Altas 最大的亮点就在于与 ASP.NET 现有机制的无缝融合。通过 VS.NET 集成开发环境,使用者可以在对 js 和 AJAX 不甚了解的情况下,以非常自然的方式使用到最先进的技术。此外直接在 js 一级提供 WebService 的调用支持,也大大降低了对 ws 技术的使用门槛。而 ASP.NET 中一直引以为豪的数据绑定等技术,也可以在 Altas 中无缝得到支持,让现有投资能够最大限度得到保护。从这些意义上来说,虽然 Altas 在 AJAX 理念上没有太多突破,但不失为一个强大且实用的 AJAX 框架,非常符合 MS 在技术运用上的一贯原则。

Altas 与 .NET 下其它 AJAX 框架的横行比较,可以参考这篇文章

Welcome to my comparison of AJAX frameworks for ASP.NET

[1] 整体结构

从整体结构上来看,Altas 的核心在于 这个标签,所有支持 Altas 的页面都必须有且只有一个此标签,以引入 Altas 的基础架构支持。在此基础上,通过 标签定义需要异步更新的范围,避免传统 Post Back 模式下的全页面刷新。而需要支持 AJAX 模式获取数据的控件,则可以通过 js 脚本和 xml 脚本两种方式定义,并由 Altas 框架进行动态 patch 以实现标准 web 控件的 AJAX 支持。此外就是 WebService 调用和数据绑定的支持机制,也是利用 Altas 框架的基础架构实现的。

1.1 ScriptManager

首先,ScriptManager 是一个容器,用户可以在 ScriptManager 标签下定义期望引用的其它 js 库,以及希望通过 js 直接调用的 WebService 服务。
例如在如下的定义中,ScriptManager 控件将保存对两个客户端 js 库和 ComplexService 服务的引用,并在页面 Render 的时候写入适当的支持代码。我们可以通过 ScriptManager.Scripts 和 ScriptManager.Services 属性访问类似定义。
1 2 EnableScriptComponents="True" EnablePartialRendering="True">
3 scripts>
4
5
6
7
8
9

10



其中 ScriptReference 非常简单,支持通过 ScriptName 或 Path 属性指定脚本。
ScriptName 指定 Altas 内建的库名称,在 FrameworkScript 类型中有具体定义。这个属性在有的文档和例子中,也直接称为 Name 属性,但最新的 Altas M1 中已改为 ScriptName。这个脚本类型将被通过 ScriptManager.ConvertFrameworkScriptToFileName 函数转换为对应的 js 文件名。
1public enum FrameworkScript
2{
3 Custom,
4 AtlasUIDragDrop, // "AtlasUIDragDrop.js";
5 AtlasUIGlitz, // "AtlasUIGlitz.js";
6 AtlasUIMap // "AtlasUIMap.js";
7}


如果直接使用 Path 则可以指定任意的用户自定义库。

此外还可以通过 ScriptReference.Browser 属性指定脚本适用于的浏览器,Altas 将根据客户端浏览器类型,自动选择加载合适的脚本。

而 ServiceReference 也非常类似,可以通过 Path 和 Type 属性指定 WebService 的 .asmx 路径和相关类型。如果 GenerateProxy 属性为 true 的话(缺省),则 ScriptManager 会为此服务自动生成 proxy 包装脚本;否则将依赖于后台的自动处理机制提供支持。具体的 WebService 实现原理,等后面进行分析时在详细解释。目前需要知道的是,如果打开 GenerateProxy 模式,则 Altas 会自动生成 proxy 包装脚本,并与 Scripts 中脚本一同在合适的时候写到页面。

除了 Scripts 和 Services 两类显式的元素外,ScriptManager 还提供 IScriptService 和 IScriptControl 两类接口实现对象的管理。
前者提供 Altas 自身的服务支持,例如用于提供诊断 API 的 ProfileScriptService 组件。
后者提供 Altas 服务端控件支持,例如用于服务端定时器的 TimerControl 控件。

所有这些涉及脚本的引用,都会在 ScriptManager.OnPagePreRenderComplete 事件中,调用 RenderXmlScript 方法写入到一个 xml 脚本中。
1script src="ScriptLibrary/Atlas/Debug/Atlas.js" type="text/javascript">/script>
2
3script type="text/xml-script">
4page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
5 references>
6 add src="ScriptLibrary/Atlas/Debug/AtlasUIMap.js" />
7 add src="MyScripts/MyScript.js" />
8 add src="ComplexService.asmx/js" />
9 /references>
10
11/page>
12/script>


值得注意的是,Altas 会自动根据 web.config 中 system.web/compilation 的配置,选择 Debug 或 Release 模式的脚本。Release 模式脚本删去了多余的空格等修饰负荷,少了一些调试方面的支持。如果希望对 Altas 的脚本直接进行修改,别忘了两个版本的代码进行同步。

此外,ScriptManager 是一个协调者,它自身维护了一些常用的状态,并会根据状态来切换 Altas 引擎的工作机制。
最常使用的是 EnablePartialRendering 和 EnableScriptComponents 属性。

EnablePartialRendering 属性决定是否启用局部重绘的模式。
传统的 Post Back 模式页面,在用户 submit 时会重绘整个页面,并导致浏览器显式的闪烁。而在基于 AJAX 技术的 Altas 框架中,可以通过 UpdatePanel 标签指定需要重绘的局部。这样一来页面在处理请求时,会首先根据 ScriptManager.IsInPartialRenderingMode 属性判断是否在重绘模式中。如果在重绘模式,则仅仅将需要重绘的 UpdatePanel 内容,返回给客户端浏览器,并由 Altas 自动进行内容的更新。通过这种模式,使用者可以在对代码几乎无需修改的情况下,直接享受到 AJAX 带来的客户端用户体验的提升。

EnableScriptComponents 属性决定是否启用 XML 脚本模式。
XML 脚本模式是 Altas 引入的基于 XML 的描述性组件定义模型,可以通过一组 XML 标签,定义页面中已有 Web 组件的 AJAX 行为,而无需对现有组件进行修改和调整。而且因为所有的行为都是由 Altas 引擎在客户端动态绑定,所以组件的目标也可不仅仅限于现有的 Web 组件。具体的介绍可以参考 Atlas XML Script。而对于某些特殊情况,例如 ASP.NET 2.0 中的 master 页面,可以通过此属性关闭 XML 脚本支持,以大幅度简化页面的功能,此时 Altas 会自动使用 AtlasRuntime.js (57K) 替换完整的 Atlas.js (174K) 脚本。

最后,在了解了 ScriptManager 的基本职责后,我们来看看它的实现。
1public interface IScriptControlContainer
2{
3 IScriptControl RegisterControl(Control control);
4}
5
6public class ScriptManager : System.Web.UI.Control, IScriptControlContainer, IPostBackDataHandler {}
7


ScriptManager 是一个 Web 界面控件,可以直接在 VS.NET 的设计界面中进行调整;它实现了 IScriptControlContainer 接口以作为 IScriptControl 的容器,对注册到容器的不支持 IScriptControl 接口的类型,将自动建立 GenericScriptControl 进行包装;实现 IPostBackDataHandler 接口则是用于在 Post Back 时处理局部重绘的支持。

在重载的 Control.OnInit 方法中,将根据页面请求头中 delta 属性是否为 true 来判断,当前控件是否处于局部重绘中。如果是局部重绘模式,则关闭页面的 trace 模式,并接管页面 LoadComplete 事件,并最终根据每个 UpdatePanel 的重绘状态,返回实际的重绘结果。此外无论是否局部重绘,都会接管页面的 PreRenderComplete 事件,以便完成前面提到的 Altas.js 和 XML 脚本的输出。伪代码如下:
1protected override void OnInit(EventArgs e)
2{
3 // 当不处于设计模式,且控件属于某个页面时
4 if (!DesignMode && (_page != null))
5 {
6 // 判断页面中是否只有一个 ScriptManager 实例,否则抛出异常
7
8 // 如果页面请求中 delta 属性为 true 则处于重绘模式
9 if (_page.Request.Headers["delta"] == "true")
10 {
11 _inPartialRenderingMode = true; // 处于重绘模式
12 _page.TraceEnabled = false; // 关闭 trace 支持
13
14 // 根据每个 UpdatePanel 的重绘状态,返回实际的重绘结果
15 _page.LoadComplete += new EventHandler(this.OnPageLoadComplete);
16 }
17
18 // 完成前面提到的 Altas.js 和 XML 脚本的输出
19 _page.PreRenderComplete += new EventHandler(this.OnPagePreRenderComplete);
20 }
21}


在重载的 Control.OnPreRender 方法中,将针对一系列约束条件进行检查。
首先,页面必须有 form runat="server"> 标签,否则 ASP.NET 无法建立顶级 form 供 Altas 接管相应 submit 事件。
其次,如果在局部重绘模式中,则不对页面提供 Post Back 后滚动位置的维护支持,打开此模式则抛出异常。
然后,如果在局部重绘模式中,则页面必须有 head runat="server"> 标签,以便将名为 .atlas__delta 的 CSS style 挂靠在上面。不过这个限制似乎牵强了一点,目前还不知道为什么必须如此,待进一步分析。
实现的伪代码如下:
1protected override void OnPreRender(EventArgs e)
2{
3 // 页面必须有 form runat="server"> 标签,否则 ASP.NET 无法建立顶级 form 供 Altas 接管相应 submit 事件
4 if (Page.Form == null)
5 throw new InvalidOperationException("Must have a form runat=\"server\">");
6
7 // 是否在局部重绘模式中
8 if (EnablePartialRendering)
9 {
10 // 不对页面提供 Post Back 后滚动位置的维护支持,打开此模式则抛出异常
11 if (Page.MaintainScrollPositionOnPostBack)
12 throw new InvalidOperationException("MaintainScrollPostition is not supported on pages with partial rendering turned on.");
13
14 // 页面必须有 head runat="server"> 标签,以便将名为 .atlas__delta 的 CSS style 挂靠在上面
15 if (Page.Header == null)
16 throw new InvalidOperationException("Must have a head runat=\"server\">");
17
18 // 名为 .atlas__delta 的 CSS style
19 Style style = new Style();
20 style.Font.Name = "Lucida Console";
21 Page.Header.StyleSheet.CreateStyleRule(style, null, ".atlas__delta");
22 }
23}


在页面重绘的准备工作完成后,OnPagePreRenderComplete 方法会被调用。

如果是在局部重绘模式中,则直接接管 Page 的 Render 方法。
否则将根据浏览器类型,以及是否启用 XML 脚本模式来选择加载合适的 Altas 核心脚本。
最后,如果存在任意一种脚本服务、控件或引用,则调用 RenderXmlScript 函数输出 XML 脚本。然后会输出客户端代理脚本或局部重绘模式的初始化脚本。
实现的伪代码如下:
1private void OnPagePreRenderComplete(object sender, EventArgs e)
2{
3 // 是否在局部重绘模式中
4 if (_inPartialRenderingMode)
5 {
6 // 接管 Page 的 Render 方法
7 Page.SetRenderMethodDelegate(new RenderMethod(RenderPageCallback));
8 return;
9 }
10
11 // 获取客户端浏览器类型
12 string browser = _page.Request.Browser.Browser;
13
14 if (browser == "IE")
15 RegisterFrameworkScript("AtlasCompat.js");
16 else if(browser == "AppleMAC-Safari")
17 RegisterFrameworkScript("AtlasCompat2.js");
18
19 // 是否启用 XML 脚本模式
20 if (_effectiveEnableScriptComponents)
21 RegisterFrameworkScript("Atlas.js");
22 else
23 RegisterFrameworkScript("AtlasRuntime.js");
24
25 if (存在任意一种脚本服务、控件或引用)
26 {
27 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
28
29 // 输出 XML 脚本
30 writer.Write("script type=\"text/xml-script\">");
31 RenderXmlScript(writer);
32 writer.Write("/script>");
33
34 // 是否需要输出初始化脚本
35 string proxyScript = GetClientProxyScript();
36
37 if (proxyScript != null || _enablePartialRendering)
38 {
39 writer.Write("script type=\"text/javascript\">");
40
41 // 输出客户端代理脚本
42 if (proxyScript != null)
43 writer.Write(proxyScript);
44
45 // 输出局部重绘模式初始化脚本
46 writer.WriteLine("Web.WebForms._PageRequest._setupAsyncPostBacks(document.getElementById('" + _page.Form.ClientID + "'), '" + UniqueID + "');");
47
48 writer.Write("/script>");
49 }
50
51 // 将上述脚本注册到页面的客户端脚本管理器
52 _page.ClientScript.RegisterStartupScript(typeof(ScriptManager), "ScriptManager", writer.ToString(), false);
53 }
54}


至此,我们对 Altas 的核心组件 ScriptManager 的大致结构已经有了初步的了解,接下来会针对基于 UpdatePanel 的局部重绘模式、基于 XML 脚本的声明式定义、基于 XMLHTTP 的后台数据通讯机制、以及 WebService 和 DataBinding 支持的实现机制进行分析。

1.2 UpdatePanel 与局部重绘模式 (Partial Rendering Mode)

在上一节介绍 Altas 整体结构时曾经提到,可以在启用局部重绘模式的情况下,通过通过 标签定义需要异步更新的范围。
我们知道,传统的 HTTP 协议应用场景中,客户端在用户点击 submit 提交 form 的时候,一个 GET/POST 请求被发送到后台服务器;服务器则根据 form 的 action 指定页面,调用相应的处理者返回 HTML 格式的文本;返回结果并最终由客户端在浏览器中绘制,通常导致浏览器一次明显的刷新。
这种模式从 Web 早期的 CGI 一直沿用到现在的 ASP.NET 中。其优点是简单易用且较为成熟,缺点则是刷新明显且速度慢。因为一个页面中可能大多数内容在此次请求中是无需改变的,而这部分冗余的内容在每次请求都会被反复传输。尤其是对一些交互性较强的页面,每个操作都沿用这个冗长的流程,响应速度和负载都是难以容忍的。期间大家也想过很多缓解方法,例如使用 iframe 等嵌入帧封装独立部件,或者在服务器端针对不同区域进行缓存等等,但因为都还是基于这个传统思路,无法从本质上解决问题。
而对遵循 AJAX 思想的 Altas 框架,则是大大迈出一步,真正实现按需出发进行重绘。
首先,页面在定义时可以根据逻辑被分成若干个更新区域,通过 标签直接定义。
其次,Altas 将接管 ASP.NET 客户端的顶级 Post Back 用 form,并针对局部重绘模式加入特定的参数。
然后,Altas 将接管 ASP.NET 服务器端的页面重绘方法。如果是在局部重绘模式下,则对客户端请求进行解析,并判断需要对那些区域进行重绘。可以通过在 UpdatePanel 中指定重绘条件,来避免不必要的重绘操作。
最后,重绘的结果会被封装成 XML 脚本,通过异步的 XMLHTTP 方式传递会客户端。客户端 Altas 引擎对返回内容进行解析后,更新到页面的相应控件上。

整个过程完全由 Altas 自动在后台完成,不会对前台页面造成刷新或其它的影响。

接下来我们来详细了解一下,Altas 是如何完成这一奇妙的功能。

首先,在 ScriptManager 启用局部重绘模式后(ScriptManager.EnablePartialRendering = true),可以通过 UpdatePanel 定义任意多个更新区域,例如:
1
2
3 Shipping Address:
4

5

6

7

8

9



更新区域的实际内容,在 ContentTemplate 属性定义。UpdatePanel.ContentTemplate 是一个 ITemplate 接口类型的属性。ASP.NET 通过此接口来定义服务端控件与其子控件的关系,定义如下:
1[ParseChildren(true), PersistChildren(false)]
2public class UpdatePanel : Control
3{
4 [TemplateInstance(TemplateInstance.Single), PersistenceMode(PersistenceMode.InnerProperty)]
5 public ITemplate ContentTemplate { get; set; }
6}


而如果希望显式指定更新的触发条件,则可以通过 Triggers 属性定义,例如下列代码指定,只有在触发了 btnTrigger 按钮的 Click 事件后,才需要对 UpdatePanel2 区域进行重绘。
12 OnClick="btnTrigger_Click" />
3..
4
5
6
7

8..
9



触发条件目前支持针对控件事件和内容的两类: ControlEventTrigger 和 ControlValueTrigger。所有触发条件都继承自 UpdatePanelTrigger 抽象类。
1public abstract class UpdatePanelTrigger
2{
3 internal UpdatePanelTrigger();
4 protected internal abstract bool HasTriggered(Control ownerControl);
5 protected internal virtual void Initialize(Control ownerControl);
6 internal void SetOwner(UpdatePanelTriggerCollection owner);
7
8 private UpdatePanelTriggerCollection _owner;
9}


UpdatePanel 在调用 Initialize 进行初始化的时候,会调用每个 UpdatePanelTrigger 的 Initialize 方法。具体的实现可在此事件中,接管服务器框架的相应事件,或者保存服务器控件的当前值。值得注意的是,这里的通过 ControlEventTrigger.EventName 指定的是服务器端控件的事件名称,而不是 HTML 控件的事件名称。因此上述例子的按钮点击事件,名称是 Click 而不是 onclick。而在 Altas 进行服务器端局部重绘时,会询问每个 UpdatePanel 是否需要进行重绘。此时被检查的 UpdatePanel.RequiresUpdate 属性,实际上会调用每个 UpdatePanelTrigger 的 HasTriggered 方法,判断是否需要对此 UpdatePanel 进行重绘。伪代码如下:
1public class UpdatePanel : Control
2{
3 protected internal virtual void Initialize()
4 {
5 if (_triggers != null)
6 {
7 if (ScriptManager.GetCurrent(Page).IsInPartialRenderingMode)
8 {
9 _triggers.Initialize(this);
10 }
11 }
12 }
13}
14public sealed class UpdatePanelTriggerCollection : Collection
15{
16 internal void Initialize(Control ownerControl)
17 {
18 foreach(UpdatePanelTrigger trigger in this)
19 {
20 trigger.Initialize(ownerControl);
21 }
22 }
23}


UpdatePanel.RequiresUpdate 判断是否需要重绘的代码与之基本类似。

而在 UpdatePanel.OnInit 事件中,则负责在局部重绘模式时,调用 ScriptManager.RegisterUpdatePanel 方法将自己注册到管理器中。然后注册 Page.InitComplete 事件,在 UpdatePanel.OnPageInitComplete 事件处理函数中,初始化自身。伪代码如下:
1public class UpdatePanel : Control
2{
3 protected override void OnInit(EventArgs e)
4 {
5 if (!DesignMode)
6 {
7 // 如果没有指定 UpdatePanel 则抛出异常
8 if (string.IsNullOrEmpty(ID))
9 throw new InvalidOperationException("UpdatePanel controls must have an explicit ID.");
10
11 // 如果没有找到 ScriptManager 也抛出异常
12 ScriptManager manager = ScriptManager.GetCurrent(this.Page);
13
14 if (manager == null)
15 throw new InvalidOperationException("An UpdatePanel requires a ScriptManager on the page");
16
17 // 如果启用局部重绘模式,则将自己注册到管理器
18 if (manager.IsInPartialRenderingMode)
19 manager.RegisterUpdatePanel(this);
20
21 // 页面初始化完成时对自身进行初始化
22 Page.InitComplete += new EventHandler(OnPageInitComplete);
23
24 // 处理模板控件相关事宜
25 //
26 }
27 }
28
29 private void OnPageInitComplete(object sender, EventArgs e)
30 {
31 // 仅在第一次初始化页面时对 UpdatePanel 进行初始化
32 if (!Page.IsPostBack)
33 if (ScriptManager.GetCurrent(this.Page).EnablePartialRendering)
34 Initialize();
35
36 _initialized = true;
37 }
38}


最后,如果 UpdatePanel 需要进行完整重绘时,Page 会调用 UpdatePanel 从 Web.UI.Control 重载来的 void Render(HtmlTextWriter writer) 和 void RenderChildren(HtmlTextWriter writer) 方法进行绘制。后者会根据是否启用重绘模式,用一个 标签将 ContentTemplate 中的子内容保护起来,用户在最终更新内容时定位。

了解了 UpdatePanel 的使用和实现后,我们回过头来看看 ScriptManager 是如何使用它们的。

在上一节我们曾提到,ScriptManager 在 OnPagePreRenderComplete 事件中,根据当前状态决定是否写入局部重绘模式的初始化脚本。
1private void OnPagePreRenderComplete(object sender, EventArgs e)
2{
3 //
4
5 if (存在任意一种脚本服务、控件或引用)
6 {
7 //
8
9 if (proxyScript != null || _enablePartialRendering)
10 {
11 writer.Write("script type=\"text/javascript\">");
12
13 //
14
15 // 输出局部重绘模式初始化脚本
16 writer.WriteLine("Web.WebForms._PageRequest._setupAsyncPostBacks(document.getElementById('" + _page.Form.ClientID + "'), '" + UniqueID + "');");
17
18 writer.Write("/script>");
19 }
20
21 //
22 }
23}


_setupAsyncPostBacks(...) 函数调用会在客户端载入页面时,完成 Altas 局部重绘引擎的初始化设置工作。
1Web.WebForms._PageRequestManager = function()
2{
3 this._setupAsyncPostBacks = function(form, scriptManagerID, updatePanelIDs, asyncPostbackControlIDs)
4 {
5 // 在 _PageRequest 对象中保存参数
6 _form = form;
7 _scriptManagerID = scriptManagerID;
8 _updatePanelIDs = updatePanelIDs;
9 _asyncPostbackControlIDs = asyncPostbackControlIDs;
10
11 form._initialAction = form.action;
12
13 _onsubmit = form.onsubmit;
14
15 // 接管顶级 ASP.NET 的 form 之 onsubmit/onclick 方法
16 form.onsubmit = null;
17 form.attachEvent('onsubmit', Function.createDelegate(this, this._onFormSubmit));
18 form.attachEvent('onclick', Function.createDelegate(this, this._onFormElementClick));
19
20 // 接管 ASP.NET 处理 Post Back 请求的函数
21 _originalDoPostBack = window.__doPostBack;
22 if (_originalDoPostBack) {
23 window.__doPostBack = Function.createDelegate(this, this._doPostBack);
24 }
25 }
26}


一般说来,ASP.NET 会在定义的 form id="form1" runat="server"> 附近,增加一些处理 Post Back 的客户端代码,例如:
1body>
2 form name="aspnetForm" method="post" action="MyLists.aspx" id="aspnetForm">
3div>
4input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
5input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
6input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" />
7input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="TCJEqyt9uS7OFuMId6rlbgLv + 36H71Efw5hFAgjKyJ42XauLO8blWV / ofWtkx9Pg+SZ76WA7NvDr + / KDLacJvcKBot564ONmv4RYIXk + 6GzGtINC2f4d7VDQPQXyRXwxIavJZsBZGQUwabITF0mTGs9Cus01SoG / cg2ACWQa / uofvZfU1ocGCnmKuu1SLVs6u9Y / UMOMC6lNVJgWOv3CILth90llrrIPN7nCVJC4Xyq3 + nSZhzoN0 / Oo4Xz4JMjUBFsy7KyDPXEaDHQGQXyRuA==" />
8/div>
9
10script type="text/javascript">
11!--
12var theForm = document.forms['aspnetForm'];
13if (!theForm) {
14 theForm = document.aspnetForm;
15}
16function __doPostBack(eventTarget, eventArgument) {
17 if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
18 theForm.__EVENTTARGET.value = eventTarget;
19 theForm.__EVENTARGUMENT.value = eventArgument;
20 theForm.submit();
21 }
22}
23// -->
24/script>


Altas 则通过接管上述几个客户端事件,在 ASP.NET 客户端脚本和服务端实现之间,增加了一个透明的代理层。

其中 _onFormSubmit 事件负责完成实际的局部重绘参数构建;_onFormElementClick 事件负责保存表单点击的额外信息,并传递回服务端;_doPostBack 事件则将自动的同步页面 Post Back 操作,重定向到异步的 _onFormSubmit 操作。

首先, _onFormSubmit 事件会针对各种状态进行判断
1Function.createDelegate = function(instance, method) {
2 return function() {
3 method.apply(instance, arguments);
4 }
5}
6
7Web.WebForms._PageRequestManager = function()
8{
9 this.get_inPostBack = function() {
10 return _request != null;
11 }
12
13 this._onFormSubmit = function()
14 {
15 // 如果已经是 Post Back 模式,则直接返回不重复提交
16 if (this.get_inPostBack()) {
17 if (window.event) {
18 window.event.returnValue = false;
19 }
20 return false;
21 }
22
23 // 如果 form 有 onsubmit 事件处理函数,则对其进行包装
24 var continueSubmit = true;
25
26 if (_onsubmit) {
27 continueSubmit = Function.createDelegate(this, _onsubmit);
28 }
29
30 if (!continueSubmit) {
31 if (window.event) {
32 window.event.returnValue = false;
33 }
34 return false;
35 }
36
37 // 如果表单的 action 和初始化时不同,则跳过数据处理
38 var form = _form;
39 if (form.action != form._initialAction) {
40 return true;
41 }
42
43 // 如果不启用异步的 post back 模式,则跳过数据处理
44 if (!_postbackSettings.async) {
45 return true;
46 }
47
48 // 数据处理
49 //
50 }
51}


然后,_onFormSubmit 事件会根据表单中每一个包含数据的元素,构建一个完整的请求参数表。
1Web.WebForms._PageRequestManager = function()
2{
3 this._onFormSubmit = function()
4 {
5 // 针对各种状态进行判断
6 //
7
8 // 建立 StringBuilder 用于构建请求内容
9 var formBody = new Web.StringBuilder();
10 formBody.append(_scriptManagerID + '=' + _postbackSettings.panelID + '&');
11
12 for (遍历表单中每个元素)
13 {
14 // 处理 INPUT、SELECT 和 TEXTAREA 三类标记
15 // 将其 id 和 value,拼接成 id=value&id=value&id=value 类型的数据
16 }
17
18 // 如果有额外的输入信息,也添加到请求内容中,用户记录事件等信息
19 if (_additionalInput) {
20 formBody.append(_additionalInput);
21 _additionalInput = null;
22 }
23
24 // 通过 XMLHTTP 异步发送请求
25 //
26 }
27}


最后,_onFormSubmit 将构造得到的请求内容,通过 XMLHTTP 的方式发送到服务端。
1Web.WebForms._PageRequestManager = function()
2{
3 this._onFormSubmit = function()
4 {
5 // 针对各种状态进行判断
6 //
7
8 // 根据表单中每一个包含数据的元素,构建一个完整的请求参数表
9 //
10
11 // 构建 XMLHTTP 封装类 WebRequest 实例
12 var request = new Web.Net.WebRequest();
13
14 // 填充基本请求信息,delta=true 表示启用局部重绘模式,并且关闭缓存
15 request.set_url(form.action);
16 request.get_headers()['delta'] = 'true';
17 request.get_headers()['Cache-Control'] = 'no-cache';
18 request.set_timeoutInterval(90000);
19
20 // 接管请求完成或超时的事件
21 request.completed.add(Function.createDelegate(this, this._onFormSubmitCompleted));
22 request.timeout.add(Function.createDelegate(this, this._onFormSubmitTimeout));
23
24 // 提交构造得到的请求参数表
25 request.set_body(formBody.toString());
26
27 // 进入 post back 请求状态
28 _request = request;
29 this.raisePropertyChanged('inPostBack');
30 request.invoke();
31
32 if (window.event) {
33 window.event.returnValue = false;
34 }
35 return false;
36 }
37}


当后台异步请求完成时,Altas 脚本会对返回结果进行解析并更新页面。这部分的讨论等完成服务端的结果讨论后再详细展开。
对异步请求超时的情况,仅仅是终止 post back 状态,因为信息不足以判断如何进行处理。个人觉得这种处理思路过于草率了,至少应该提供一些信息,让使用者能通过 inPostBack 属性变化事件,了解到请求到底是成功还是超时。
1Web.WebForms._PageRequestManager = function()
2{
3 this._onFormSubmitTimeout = function(sender, eventArgs) {
4 _request = null;
5 this.raisePropertyChanged('inPostBack');
6 }
7}


_doPostBack 事件基本上就是 _onFormSubmit 的封装。它会根据 post back 请求事件的来源,判断是否需要启用异步 post back 模式。
1Web.WebForms._PageRequestManager = function()
2{
3 this._doPostBack = function(eventTarget, eventArgument)
4 {
5 _additionalInput = null;
6
7 if (this.get_inPostBack()) {
8 if (window.event) {
9 window.event.returnValue = false;
10 }
11 return;
12 }
13
14 // 根据事件来源,判断是否需要启用异步模式
15 _postbackSettings = null;
16
17 var postbackElement = findNearestElement(eventTarget);
18
19 if (postbackElement)
20 _postbackSettings = getPostbackSettings(postbackElement);
21 else
22 _postbackSettings = createPostbackSettings(true, _scriptManagerID);
23
24 // 对同步模式,直接调用 ASP.NET 的 doPostBack 实现
25 if (!_postbackSettings.async) {
26 _originalDoPostBack(eventTarget, eventArgument);
27 return;
28 }
29
30 // 对异步模式,填充事件信息,并调用 _onFormSubmit 完成数据准备和提交操作
31 var form = _form;
32 form.__EVENTTARGET.value = eventTarget;
33 form.__EVENTARGUMENT.value = eventArgument;
34 this._onFormSubmit();
35
36 if (window.event) {
37 window.event.returnValue = false;
38 }
39 }
40}


至此,我们基本上完成了对局部重绘模式下,从 UpdatePanel 到客户端数据提交的分析。下一节将继续针对局部重绘模式,分析服务端对此模式下刷新并返回数据实现,以及客户端如何对返回数据进行解析,并更新到最终页面的控件。

1.3 局部重绘模式的服务器端响应

在第一小节中,我们曾提到 ScriptManager 在重载的 Web.UI.Control.OnInit 事件中,会根据页面请求中 delta = true 是否存在,判断当前页面是否处于局部重绘模式中,并接管 LoadComplete 时间来处理此模式。相应的 OnInit 事件还会在局部重绘模式中,主动接管 Page.Render 方法的逻辑来替换完整页面刷新。


1protected override void OnInit(EventArgs e)
2{
3 // 当不处于设计模式,且控件属于某个页面时
4 if (!DesignMode && (_page != null))
5 {
6 // 判断页面中是否只有一个 ScriptManager 实例,否则抛出异常
7
8 // 如果页面请求中 delta 属性为 true 则处于重绘模式
9 if (_page.Request.Headers["delta"] == "true")
10 {
11 _inPartialRenderingMode = true; // 处于重绘模式
12 _page.TraceEnabled = false; // 关闭 trace 支持
13
14 // 根据每个 UpdatePanel 的重绘状态,返回实际的重绘结果
15 _page.LoadComplete += new EventHandler(this.OnPageLoadComplete);
16 }
17
18 // 完成前面提到的 Altas.js 和 XML 脚本的输出
19 _page.PreRenderComplete += new EventHandler(this.OnPagePreRenderComplete);
20 }
21}
22
23private void OnPagePreRenderComplete(object sender, EventArgs e)
24{
25 // 是否在局部重绘模式中
26 if (_inPartialRenderingMode)
27 {
28 // 接管 Page 的 Render 方法
29 Page.SetRenderMethodDelegate(new RenderMethod(RenderPageCallback));
30 return;
31 }
32
33 //
34}

在 OnPageLoadComplete 中,将遍历通过 RegisterUpdatePanel 注册到 ScriptManager 的所有 UpdatePanel,评估哪些区域是真正需要进行更新的 (UpdatePanel,评估哪些.RequiresUpdate = true),伪代码如下:


1private void OnPageLoadComplete(object sender, EventArgs e)
2{
3 for(UpdatePanel panel in _allUpdatePanels)
4 {
5 if(panel 是 Page.Form 的子控件 && panel.RequiresUpdate)
6 {
7 panel.SetPartialRenderingMode(true);
8 _updatePanels.Add(panel1);
9 }
10 }
11}
而 RenderPageCallback 中,则将取代 Page.Render 的原本逻辑,根据整理出的 _updatePanels 列表中的区域进行重绘。返回的内容将是一个 XML 格式的文档,包括重绘的内容()、重绘的区域()以及相关 XML 脚本()等。实现的伪代码如下:


1private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
2{
3 Page page = (Page) pageControl;
4 HttpResponse response = page1.Response;
5
6 // 关闭 HTML 缓存,设置返回文档类型为 text/xml
7 response.Cache.SetCacheability(HttpCacheability.NoCache);
8 response.ContentType = "text/xml";
9
10 // 输出 HTML 头内容
11 writer.Write("");
12 page.Header.RenderControl(writer);
13
14 // 输出 Form 成员的内容
15 HtmlForm form = page.Form;
16 form.SetRenderMethodDelegate(new RenderMethod(this.RenderFormCallback));
17 form.RenderControl(writer);
18
19 writer.Write("
");
20
21 // 输出重绘 UpdatePanel 的 ID 列表
22 writer.Write("");
23 for (UpdatePanel panel in _updatePanels)
24 {
25 // 添加逗号分隔符
26
27 writer.Write(updatePanels.ClientID);
28 }
29 writer.Write("
");
30
31 // 输出 XML 脚本,如引用等
32 writer.Write("");
33 RenderXmlScript(writer);
34 writer.Write("
");
35 writer.Write("
");
36}

实际的针对控件的重绘逻辑,在 RenderFormCallback 中完成。此函数将针对 _updatePanels 中保存的需要进行重绘的区域,调用其 RenderControl 方法绘制整个子控件树。如果 Page.EnableEventValidation 选项打开,还会通过一个空 HtmlTextWriter 来模拟调用所有的控件输出,来模拟完整的事件引发流程。但其输出的内容被直接抛弃,避免冗余内容通过网络传输。完整的伪代码如下:


1private void RenderFormCallback(HtmlTextWriter writer, Control containerControl)
2{
3 for (UpdatePanel panel in _updatePanels)
4 {
5 panel.RenderControl(writer);
6 }
7
8 if (Page.EnableEventValidation)
9 {
10 DummyHtmlTextWriter writer = new DummyHtmlTextWriter();
11
12 for (Control control in containerControl.Controls)
13 {
14 control.RenderControl(writer);
15 }
16 }
17}
而在客户端浏览器中,依照上节中的分析,后台更新请求将通过 Web.Net.WebRequest 的封装,以 XMLHTTP 方式发送给页面;处理结果将由 request 对象上注册的 _onFormSubmitCompleted 事件进行解析。
_onFormSubmitCompleted 事件中,首先会对请求的返回状态进行检测,如果出错则进入错误模式并返回;如果返回正常,则先检查请求返回值是否是重定向命令,是则刷新窗口到新地址并返回;然后会根据前面提到的 deltaPanels 标签中 ID 列表,调用 _updatePanel 函数分别对每个区域进行更新;最后,会对隐藏的 input 域、页面标题、HTML 头中的 css 以及 XML 脚本等特殊标签进行处理。伪代码如下:


1_onFormSubmitCompleted = function(sender, eventArgs)
2{
3 var isErrorMode = true; // 是否处于错误模式
4 var response = sender.get_response();
5 var delta; // 实际返回的更新内容
6
7 // 请求成功则对返回内容进行解析
8 if (response.get_statusCode() == 200)
9 {
10 if(delta = response.get_xml())
11 {
12 // 对 IE 浏览器来说,选择 XPath 作为解析语言
13 if (Web.Application.get_type() == Web.ApplicationType.InternetExplorer)
14 delta.setProperty('SelectionLanguage', 'XPath');
15
16 // 返回内容中如果有 pageError 节点则说明服务器端处理出现异常
17 if (errorNode = delta.selectSingleNode("/delta/pageError"))
18 isErrorMode = false;
19 }
20 }
21
22 // 如果发生错误则进入错误模式
23 if (isErrorMode)
24 {
25 _enterErrorMode(errorNode ? errorNode.attributes.getNamedItem('message').nodeValue : 'Unknown error');
26 return;
27 }
28
29 // 如果有页面重定向命令则重定向窗口
30 if (redirectNode = delta.selectSingleNode("/delta/pageRedirect"))
31 {
32 window.location = redirectNode.attributes.getNamedItem('location').nodeValue
33 return;
34 }
35
36 for(遍历 delta.selectSingleNode("/delta/deltaPanels/text()") 中每个节点)
37 {
38 _updatePanel(deltaPanelID, 目标区域);
39 }
40
41 for(遍历 delta.selectNodes('/delta/rendering//input[@type="hidden"]') 中每个隐藏 input 域)
42 {
43 // 向 page.form 中插入新的隐藏域
44 }
45
46 // 如果有 title 节点则修改文档标题
47 var title = delta.selectSingleNode('/delta/rendering//title/text()')
48 document.title = title ? title.nodeValue.trim() ? '';
49
50 // 如果有 style 节点则更新 css
51 if (styleSheetMarkup = delta.selectSingleNode('/delta/rendering/head/style[position()=last()]'))
52 _updateStyleSheet(styleSheetMarkup.text);
53
54 // 如果有脚本节点则更新脚本,否则调用 _onFormSubmitCompletedCallback 完成解析
55 if (scripts = delta.selectNodes('/delta/rendering//script[@type="text/javascript"]'))
56 _updateScripts(scripts);
57 else
58 _onFormSubmitCompletedCallback();
59}
这里对异常的处理,是 Altas M1 版本新增的功能。在前面所分析的 RenderPageCallback 方法中,通过一个 try...catch 将完整的局部重绘页面操作保护起来。如果有异常发生,则调用 OnPageError 事件进行实际处理,并最终通过 OnError 方法将异常信息返回给调用客户端。伪代码如下:


1private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
2{
3 // 设置返回内容格式类型等
4
5 writer.Write("");
6
7 try
8 {
9 // 局部重绘页面操作
10 }
11 catch(Exception e)
12 {
13 OnPageError(e);
14 }
15
16 writer.Write("
");
17}
18
19private void OnPageError(Exception ex)
20{
21 PageErrorEventArgs args = new PageErrorEventArgs(ex);
22 OnPageError(args);
23 ScriptManager.OnError(args.ErrorMessage, _page.Server, _page.Response);
24}
25
26private static void OnError(string errorMessage, IHttpServerUtility httpServer, IHttpResponse response)
27{
28 httpServer.ClearError();
29 response.Clear();
30 response.Cache.SetCacheability(HttpCacheability.NoCache);
31 response.ContentType = "text/xml";
32 response.Write("");
33 response.Write("");
34 response.Write("
");
35}
而在 ScriptManager 中,可以通过 ErrorTemplate 标签定义异常信息的显式模板,类似


atlas:ScriptManager runat="server" >
ErrorTemplate>
There was an error processing your action.

span id="errorMessageLabel">

hr />
button type="button" id="okButton">OK
/ErrorTemplate>
/atlas:ScriptManager>
而 ScriptManager.RenderErrorTemplate 方法会根据模板内容,生成名称为 __ErrorContainer 的 HTML 元素,并最终在客户端解析返回值的 _enterErrorMode 函数中进行更新。

而对重绘区域进行更新的 _updatePanel 函数,将根据 deltaPanels 中给出的 ID,定位到目标的更新区域 span 标签。并将其所有子控件进行析构 (dispose) 和删除 (removeChild),并用 rendering 中返回的内容替换之。


1_updatePanel = function(panelID, rendering)
2{
3 var updatePanelElement = document.getElementById(panelID);
4
5 var elementsToDestroy = [];
6 var childCount = updatePanelElement.children.length;
7
8 for (var i = 0; i < childCount; i++)
9 {
10 elementsToDestroy.add(updatePanelElement.children[i]);
11 }
12
13 for (var j = 0; j < elementsToDestroy.length; j++)
14 {
15 if (elementsToDestroy[j].control)
16 elementsToDestroy[j].control.dispose();
17
18 updatePanelElement.removeChild(elementsToDestroy[j]);
19 }
20
21 updatePanelElement.innerHTML = rendering;
22}

除了上述异常处理的流程外,Altas M1 还在 OnInit 方法中接管了局部重绘模式下的 IHttpContext.ApplicationInstance 对象的 PreSendRequestHeaders 和 Error 事件,分别用于处理页面重定向和全局异常的情况。具体实现机制与上述异常处理机制较为类似,这里就不一一分析了。

至此,一个完整的 Altas 异步请求和局部重绘模式的流程就基本分析完成了,后面有时间将继续就 WebService 支持、数据绑定等实现进行分析,而其原理基本上都是基于之前两节所分析的模式,只不过根据具体的应用有所变化。

(按:我的笔记
容器 ScriptManager
|
|--自身加载卸载
|
|--自身状态维护:局部重绘模式,目前局部重绘状态
|
|--重载接管消息/事件,功能调用
|
|--出错/异常处理,通过 ErrorTemplate 标签定义异常信息的显式模板
|
|--利用Net接口,方便使用Web界面控件的已有特性,不过有时不够轻量
|
- - - - - - - - - - - - - - - - - - - - - -
|
|--服务/函数 的定位,加载卸载
|
|--服务/函数 的类型判断,功能读取,XML描述性组件定义模型,注册脚本
|
|--根据事件来源,判断是否需要启用异步模式

服务器
|
|--检查ScriptManager获取相关信息
|
|--关闭 HTML 缓存,针对浏览器选择解析语言
|
|--遍历已经注册到 ScriptManager 的所有控件,更新


服务器的响应没有预计到的两点:

1. 如果 Page.EnableEventValidation 选项打开,还会通过一个空 HtmlTextWriter 来模拟调用所有的控件输出,来模拟完整的事件引发流程。但其输出的内容被直接抛弃,避免冗余内容通过网络传输。

2. 除了上述异常处理的流程外,Altas M1 还在 OnInit 方法中接管了局部重绘模式下的 IHttpContext.ApplicationInstance 对象的 PreSendRequestHeaders 和 Error 事件,分别用于处理页面重定向和全局异常的情况。
)

This page is powered by Blogger. Isn't yours?