Atlas 实现机制浅析 [3]
原文:http://www.blogcn.com/User8/flier_lu/blog/29042138.html
1.3 局部重绘模式的服务器端响应
在第一小节中,我们曾提到 ScriptManager 在重载的 Web.UI.Control.OnInit 事件中,会根据页面请求中 delta = true 是否存在,判断当前页面是否处于局部重绘模式中,并接管 LoadComplete 时间来处理此模式。相应的 OnInit 事件还会在局部重绘模式中,主动接管 Page.Render 方法的逻辑来替换完整页面刷新。
protected
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
23
private
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),伪代码如下:
private
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 格式的文档,包括重绘的内容(<rendering>)、重绘的区域(<deltaPanels>)以及相关 XML 脚本(<xmlScript>)等。实现的伪代码如下:
private
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(
"
<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 来模拟调用所有的控件输出,来模拟完整的事件引发流程。但其输出的内容被直接抛弃,避免冗余内容通过网络传输。完整的伪代码如下:
private
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 脚本等特殊标签进行处理。伪代码如下:
_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').nodeValue33
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 方法将异常信息返回给调用客户端。伪代码如下:
private
void
RenderPageCallback(HtmlTextWriter writer, Control pageControl)2
{3
//
设置返回内容格式类型等
4
5
writer.Write(
"
<delta>
"
);6
7
try
8
{9
//
局部重绘页面操作
10
}
11
catch
(Exception e)12
{13
OnPageError(e);14
}
15
16
writer.Write(
"
</delta>
"
);17
}
18
19
private
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
26
private
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(
"
<delta>
"
);33
response.Write(
"
<pageError message=\
""
+ HttpUtility.HtmlAttributeEncode(errorMessage) +
"
\
"
/>
"
);34
response.Write(
"
</delta>
"
);35
}
而在 ScriptManager 中,可以通过 ErrorTemplate 标签定义异常信息的显式模板,类似
<
atlas:ScriptManager
runat
="server"
>
<
ErrorTemplate
>
There was an error processing your action.
<
br
/>
<
span
id
="errorMessageLabel"
></
span
>
<
hr
/>
<
button
type
="button"
id
="okButton"
>
OK
</
button
>
</
ErrorTemplate
>
</
atlas:ScriptManager
>
而 ScriptManager.RenderErrorTemplate 方法会根据模板内容,生成名称为 __ErrorContainer 的 HTML 元素,并最终在客户端解析返回值的 _enterErrorMode 函数中进行更新。
而对重绘区域进行更新的 _updatePanel 函数,将根据 deltaPanels 中给出的 ID,定位到目标的更新区域 span 标签。并将其所有子控件进行析构 (dispose) 和删除 (removeChild),并用 rendering 中返回的内容替换之。
_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 支持、数据绑定等实现进行分析,而其原理基本上都是基于之前两节所分析的模式,只不过根据具体的应用有所变化。
浙公网安备 33010602011771号