现在的位置: 首页 > 综合 > 正文

Atlas 实现机制浅析 [2]

2012年08月08日 ⁄ 综合 ⁄ 共 7283字 ⁄ 字号 评论关闭

原文:http://www.blogcn.com/User8/flier_lu/blog/28981917.html

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

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

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

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

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

1<atlas:UpdatePanel runat="server" ID="UpdatePanel1">
2    <ContentTemplate>
3        <strong><span style="text-decoration: underline">Shipping Address</span>:</strong>
4            <br />
5        <asp:Label ID="lblFirstLineShipping" runat="server" Font-Bold="False"></asp:Label><br />
6        <asp:Label ID="lblSecondLineShipping" runat="server"></asp:Label><br />
7        <asp:Label ID="lblThirdLineShipping" runat="server"></asp:Label><br />
8    </ContentTemplate>
9</atlas:UpdatePanel>

   更新区域的实际内容,在 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 getset; }
6}

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

1<asp:Button runat="server" ID="btnTrigger" Text="Trigger" 
2  OnClick="btnTrigger_Click" />
3..            
4<atlas:UpdatePanel runat="server" ID="UpdatePanel2" Mode="Conditional">
5  <Triggers>
6    <atlas:ControlEventTrigger ControlID="btnTrigger" EventName="Click" />
7  </Triggers>
8..
9</atlas:UpdatePanel>

   触发条件目前支持针对控件事件和内容的两类: 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<UpdatePanelTrigger>
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) 方法进行绘制。后者会根据是否启用重绘模式,用一个 <span/> 标签将 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(thisthis._onFormSubmit));
18    form.attachEvent('onclick', Function.createDelegate(thisthis._onFormElementClick));
19    
20    // 接管 ASP.NET 处理 Post Back 请求的函数
21    _originalDoPostBack = window.__doPostBack;
22    if (_originalDoPostBack) {
23        window.__doPostBack = Function.createDelegate(thisthis._doPostBack);
24    }

25  }

26}

   一般说来,ASP.NET 会在定义的 <form id="form1" runat="server"> 附近,增加一些处理 Post Back 的客户端代码,例如:

抱歉!评论已关闭.