3. ASP.NET AJAX部分呈现剖析
3.1 先从客户端讲起
看一下上面的示例代码在客户端的HTML代码, 这里只列出核心部分,其他全部隐去。
<script type="text/javascript"> //<![CDATA[ Sys.WebForms.PageRequestManager._initialize('ScriptManager1', document.getElementById('form1')); Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tUpdatePanel1'], [], [], 90); //]]> </script> <div id="UpdatePanel1"> 7/25/2008 4:54:36 PM <br /> <input type="submit" name="Button1" value="Button" id="Button1" /> </div>看一下上面的两句JavaScript代码,第一句代码中的_initialize 方法是客户端PageRequestManager对象上的静态方法,它会创建一个 PageRequestManager 类的全局实例,并将其初始化。在这个初始化函数中,ageRequestManager对象注册了当前表单对象的submit事件,以及window对象的load和unload事件。
而第二句代码则是通过PageRequestManager的getInstance方法来检索其唯一实例, 得到该实例后调用_updateControls方法来注册UpdatePanel以及其Trigger控件。
我们可以从MicrosoftAjaxWebForm.js文件中得到_updateControls方法的声明:
function Sys$WebForms$PageRequestManager$_updateControls(updatePanelIDs, asyncPostBackControlIDs, postBackControlIDs, asyncPostBackTimeout) {}由其中第一个参数代表了当前页面上所有的UpdatePanel控件的ID集合,如果该UpdatePanel的ChildrenAsTrigger为True的话,应在ID前添加字符't',否则添加字符'f';而第二个参数是所有引发异步回送的控件ID;第三个参数是所有引发同步回送的控件ID;第四个参数设定了异步回送的Timeout时间,单位为秒。于PageRequestManager对象注册了当前表单的submit时间,所以每当页面有提交动作的时候,PageRequestManager对象就会介入,看一下PageRequestManager对象页面提交处理函数_onFormSubmit(evt)。
如果需要执行一次异步回送的话,会中止原有的普通浏览器会回发,代之使用XMLHttpRequest进行AJAX回发。在封装这个请求的时候,当前页面的所有字段以及视图状态都会被打包在请求中,另外还设置了这次Request的HTTP头:request.get_headers()['X-MicrosoftAjax'] = 'Delta=true';
在服务器端将会根据这个HTTP头标记来判定是否为一次AJAX异步回发。
_onFormSubmit(evt)函数代码:
function Sys$WebForms$PageRequestManager$_onFormSubmit(evt) { var continueSubmit = true; var isCrossPost = this._isCrossPost; this._isCrossPost = false; if (this._onsubmit) { continueSubmit = this._onsubmit(); } if (continueSubmit) { for (var i = 0; i < this._onSubmitStatements.length; i++) { if (!this._onSubmitStatements[i]()) { continueSubmit = false; break; } } } if (!continueSubmit) { if (evt) { evt.preventDefault(); } return; } var form = this._form; if (isCrossPost) { return; } if (this._activeDefaultButton && !this._activeDefaultButtonClicked) { this._onFormElementActive(this._activeDefaultButton, 0, 0); } if (!this._postBackSettings.async) { return; } var formBody = new Sys.StringBuilder(); formBody.append(encodeURIComponent(this._scriptManagerID) + '=' + encodeURIComponent(this._postBackSettings.panelID) + '&'); var count = form.elements.length; for (var i = 0; i < count; i++) { var element = form.elements[i]; var name = element.name; if (typeof(name) === "undefined" || (name === null) || (name.length === 0)) { continue; } var tagName = element.tagName; if (tagName === 'INPUT') { var type = element.type; if ((type === 'text') || (type === 'password') || (type === 'hidden') || (((type === 'checkbox') || (type === 'radio')) && element.checked)) { formBody.append(encodeURIComponent(name)); formBody.append('='); formBody.append(encodeURIComponent(element.value)); formBody.append('&'); } } else if (tagName === 'SELECT') { var optionCount = element.options.length; for (var j = 0; j < optionCount; j++) { var option = element.options[j]; if (option.selected) { formBody.append(encodeURIComponent(name)); formBody.append('='); formBody.append(encodeURIComponent(option.value)); formBody.append('&'); } } } else if (tagName === 'TEXTAREA') { formBody.append(encodeURIComponent(name)); formBody.append('='); formBody.append(encodeURIComponent(element.value)); formBody.append('&'); } } if (this._additionalInput) { formBody.append(this._additionalInput); this._additionalInput = null; } var request = new Sys.Net.WebRequest(); var action = form.action; if (Sys.Browser.agent === Sys.Browser.InternetExplorer) { var queryIndex = action.indexOf('?'); if (queryIndex !== -1) { var path = action.substr(0, queryIndex); if (path.indexOf("%") === -1) { action = encodeURI(path) + action.substr(queryIndex); } } else if (action.indexOf("%") === -1) { action = encodeURI(action); } } request.set_url(action); request.get_headers()['X-MicrosoftAjax'] = 'Delta=true'; request.get_headers()['Cache-Control'] = 'no-cache'; request.set_timeout(this._asyncPostBackTimeout); request.add_completed(Function.createDelegate(this, this._onFormSubmitCompleted)); request.set_body(formBody.toString()); var handler = this._get_eventHandlerList().getHandler("initializeRequest"); if (handler) { var eventArgs = new Sys.WebForms.InitializeRequestEventArgs(request, this._postBackSettings.sourceElement); handler(this, eventArgs); continueSubmit = !eventArgs.get_cancel(); } if (!continueSubmit) { if (evt) { evt.preventDefault(); } return; } this._scrollPosition = this._getScrollPosition(); this.abortPostBack(); handler = this._get_eventHandlerList().getHandler("beginRequest"); if (handler) { var eventArgs = new Sys.WebForms.BeginRequestEventArgs(request, this._postBackSettings.sourceElement); handler(this, eventArgs); } if (this._originalDoCallback) { this._cancelPendingCallbacks(); } this._request = request; request.invoke(); if (evt) { evt.preventDefault(); } } 我们可以发现AJAX回发所提交的数据量其实跟普通回发过程中提交的数据量是一样的,并且还附加了一些额外信息。