原文:http://www.blogcn.com/User8/flier_lu/blog/29042138.html
1.3 局部重绘模式的服务器端响应
在第一小节中,我们曾提到 ScriptManager 在重载的 Web.UI.Control.OnInit 事件中,会根据页面请求中 delta = true 是否存在,判断当前页面是否处于局部重绘模式中,并接管 LoadComplete 时间来处理此模式。相应的 OnInit 事件还会在局部重绘模式中,主动接管 Page.Render 方法的逻辑来替换完整页面刷新。
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),伪代码如下:
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 格式的文档,包括重绘的内容(<rendering>)、重绘的区域(<deltaPanels>)以及相关 XML 脚本(<xmlScript>)等。实现的伪代码如下:
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("<delta><rendering>");
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("</rendering>");
20
21 // 输出重绘 UpdatePanel 的 ID 列表
22 writer.Write("<deltaPanels>");
23 for (UpdatePanel panel in _updatePanels)
24 {
25 // 添加逗号分隔符
26
27 writer.Write(updatePanels.ClientID);
28 }
29 writer.Write("</deltaPanels>");
30
31 // 输出 XML 脚本,如引用等
32 writer.Write("<xmlScript>");
33 RenderXmlScript(writer);
34 writer.Write("</xmlScript>");
35 writer.Write("</delta>");
36}
实际的针对控件的重绘逻辑,在 RenderFormCallback 中完成。此函数将针对 _updatePanels 中保存的需要进行重绘的区域,调用其 RenderControl 方法绘制整个子控件树。如果 Page.EnableEventValidation 选项打开,还会通过一个空 HtmlTextWriter 来模拟调用所有的控件输出,来模拟完整的事件引发流程。但其输出的内容被直接抛弃,避免冗余内容通过网络传输。完整的伪代码如下:
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 脚本等特殊标签进行处理。伪代码如下:
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 方法将异常信息返回给调用客户端。伪代码如下:
2{
3 // 设置返回内容格式类型等
4
5 writer.Write("<delta>");
6
7 try
8 {
9 // 局部重绘页面操作
10 }
11 catch(Exception e)
12 {
13 OnPageError(e);
14 }
15
16