ASP.NET 3.5核心编程学习笔记(54):UpdatePanel

  UpdatePanel控件是泛化的控件容器,负责刷新所有子控件,而不需要回发整个页面。这就是所谓的“部分呈现(partial rendering)”。使用该控件,我们可以包装现有页面的局部,或用ASP.NET 2.0编程模型开发的新页面的局部。在可更新区域中发起的所有回发,都会由UpdatePanel控件管理,并且只更新该区域的控件。

UpdatePanel控件

   UpdatePanel控件是应用AJAX最简单的方式,它允许我们向现有的基于ASP.NET 2.0编程模型编写的网站添加AJAX功能。我们除了要理解UpdatePanel控件的语法和语义外,不必学习其他技术。

  部分呈现与传统的回发有何区别呢?UpdatePanel控件不会刷新整个页面,它能够截获其内部的回发请求,并为刷新标记向当前页面的URL发送out-of-band请求。在响应就绪后,它会更新DOM树。

UpdatePanel控件概述

  UpdatePanel控件定义在System.Web.Extensions程序集中,隶属于System.Web.UI命名空间中。该控件派生自Control,它只是一个能包容子控件的AJAX容器。UpdatePanel使用示例:

  
  
< asp:UpdatePanel ID = " UpdatePanel1 " runat = " server>
< ContentTemplate >
<!-- 在此处加入要包装的子控件 -->
<asp:GridView ...>...</asp:GridView>
...
</ ContentTemplate >
</ asp:UpdatePanel >

  此外,我们需要在页面中添加一个ScriptManager控件,该控件是部分呈现机制的基础。

UpdatePanel控件的编程接口

  下表列出了UpdatePanel控件中的属性:

ASP.NET 3.5核心编程学习笔记(54):UpdatePanel_第1张图片

  IsInPartialRendering属性指示UpdatePanel控件的内容是否正在被更新,但如果在页面类中定义的任何处理程序读取它的值,会发现它总会返回false。该属性只为控件开发者设计,因此,它仅在呈现阶段(包括PreRender事件之后的几个事件)会被赋予相应的值。创建UpdatePanel控件自定义版本的开发者可能要重写Render方法。在这个上下文中,他们可以利用该属性来判断当前控件是处于完整的页面刷新还中部分呈现中。

  作为页面开发者,如果要知道当前被更新的部分是否为AJAX回发执行的结果,可使用ScriptManager控件的IsInAsyncPostBack属性。

  ScriptManager控件为与UpdatePanel配合使用,还需将ScriptManager控件的EnablePartialRendering属性设为true(该值亦为默认设置)。如果该属性为false,UpdatePanel控件就会像常规的面板一样工作。

以编程方式填充UpdatePanel

  UpatePanel控件的ContentTemplate属性亦可以编程方式设置:

  
  
// 页面代码
< asp:ScriptManager ID = " ScriptManager1 " runat = " server " />
< asp:UpdatePanel ID = " UpdatePanel1 " runat = " server " >
</ asp:UpdatePanel >

// cs文件中代码
protected void Page_PreInit( object sender, EventArgs e)
{
// 动态添加UpdatePanel中的子控件
string ascx = " customerivew.ascx " ;
UpdatePanel1.ContentTemplate
= this .LoadTemplate(ascx);
}

  在PreInit事件过后,则不能设置内容模板。但在呈现页面前,我们能以编程方式向其中添加子控件。如果试图使用UpdatePanel.Controls.Add方法向Controls集合中添加子控件,将出现运行时异常。这时,我们应使用ContentTemplateContainer属性。因为,我们的目的是向内容模板添加或移除控件,而不是直接针对UpdatePanel:

  
  
public partial class Samples_Ch19_Partial_Dynamic : System.Web.UI.Page
{
private Label label1;

protected void Page_Load( object sender, EventArgs e)
{
UpdatePanel upd
= new UpdatePanel();
upd.ID
= " UpdatePanel1 " ;

// 定义一个按钮
Button button1 = new Button();
button1.ID
= " Button1 " ;
button1.Text
= " What time is it? " ;
button1.Click
+= new EventHandler(Button1_Click);

// 定义一个Liter控件
LiteralControl lit = new LiteralControl( " <br> " );

// 定义一个Label控件
Label Label1 = new Label();
Label1.ID
= " Label1 " ;
Label1.Text
= " [time] " ;

// 将控件加入UpdatePanel控件
upd.ContentTemplateContainer.Controls.Add(button1);
upd.ContentTemplateContainer.Controls.Add(lit);
upd.ContentTemplateContainer.Controls.Add(Label1);

// 将UpdatePanel控件加入到窗体中
this .Form.Controls.Add(upd);
}

protected void Button1_Click( object sender, EventArgs e)
{
Label1.Text
= DateTime.Now.ToShortTimeString();
}
}

  我们可以在页面生命周期内的任何阶段将UpdatePanel控件添加到页面,也可以在任何时期向现有的UpdatePanel控件添加子控件。

母版页与可更新区域

  我们可以在母版页中安全地使用UpdatePanel控件。但有些情况需要注意:

  1. 默认情况下,如果将ScriptManager控件添加到母版页中,所有内容页便获得部分呈现功能。此外,所有内容页继承母版页中脚本管理器的初始设置。如果某个内容页要更改脚本管理器的话,我们不能添加新的脚本管理器,而应该获取定义在母版页中的实例。如下所示:

  
  
protected void Page_Init( object sender, EventArgs e)
{
ScriptManager.GetCurrent(
this ).EnableScriptLocalization = true ;
}

  2. 在内容页中,我们亦可声明ScriptManagerProxy的引用,更改其中的设置,这个代理会获取当前使用的脚本管理器。

  在执行异步AJAX回发期间,我们不能在回发事件处理程序(如,Button1_Click)中调用Response.Write。如果这样做,则会收到客户端异常,提示无法解析来自服务器的消息。一般来讲,对Response.Write的调用会向返回给客户端的流中添加额外数据,进而破坏其预期的格式。

UpdatePanel控件的优化使用

  单个ASP.NET页面可以包含多个UpdatePanel控件。UpdatePanel控件在满足以下条件时将对内容进行更新:

  1. 同一个页面中的另一个UpdatePanel控件执行了刷新。

  2. UpdatePanel所包含的任意一个子控件发起了刷新。

  3. 页面处理了一个调用UpdatePanel控件Update方法的事件。

  4. 如果UpdatePanel控件嵌套在另一个UpdatePanel控件内部,父UpdatePanel被更新时,子UpdatePanel亦会被更新。

  5. UpdatePanel的任意触发器事件被触发。

  我们可通过一系统属性(如UpdateMode、ChildrenAsTriggers、Triggers集合)来控件这些条件,实现在最大程度上降低回发次数和传输数据量。

条件刷新的配置

  默认情况下,页面中所有UpdatePanel是同步的,同时刷新。要想使每个UpdatePanel独立于其他UpdatePanel进行刷新,应该更改其UpdateMode属性。该属性的默认值为Always,表明页面中任何位置(即包括UpdatePanel以内,也包括UpdatePanel以外)引起的回发都会更新该面板内容。

  通过将UpdateMode属性设为Conditinal,我们可使UpdatePanel仅当显式发起刷新操作时才更新其内容。显式发起刷新的操作包括调用Update方法、截获子控件的回发请求及作为触发器的事件被触发。

  通常,任何定义在UpdatePanel控件内部的子控件都会充当隐式的触发器。如果将ChildrenAsTriggers属性设置为false,可防止子控件变为触发器。在这种情况下,单击UpdatePanel中的按钮,会引发常规的完整回发。

  如果只希望将UpdatePanel中的部分控件作为触发器,应该怎么做?我们可单独将这些控件定义为另一个UpdatePanel的触发器,也可使用ScriptManager类的RegisterAsyncPostBackControl方法。RegisterAsyncPostBackControl方法能对需要执行异步回发的控件进行注册。如下所示:

  
  
protected void Page_Load( object sender, EventArgs e)
{
ScriptManager1.RegisterAsyncPostBackControl(Button1);
}

  作为参数传入的控件对象将不会包含在任何可更新面板中,也不会作为触发器。该控件引起回发的最终效果与页面中UpdatePanel控件的数目有关。下面代码演示了在使用1--2个UpdatePanel控件时,整个页面的行为:

//当前页面只有一个UpdatePanel控件
protected void Button1_Click(object sender, EventArgs e)
{
//该Label控件被包含在UpdatePanel控件中,其Text值将在客户端更新
Label1.Text = "Last update at: " + DateTime.Now.ToLongTimeString();

//该Label控件未包含在UpdatePanel控件中,其Text值将不会被更新
Label2.Text = "Last update at: " + DateTime.Now.ToLongTimeString();
}

  如果有多个UpdatePanel控件,那么要想触发更新,必须在刷新时显式地调用UpdatePanel的Update方法:

//当前页面有多个UpdatePanel控件
protected void Button1_Click(object sender, EventArgs e)
{
//该Label控件被包含在UpdatePanel控件中,其Text值将在客户端更新
Label1.Text = "Last update at: " + DateTime.Now.ToLongTimeString();
//显式更新UpdatePanel控件
UpdatePanel1.Update();
}

  如果ChildrenAsTriggers的值为true,那么位于UpdatePanel控件中的所有子控件都会作为参数传入RegisterAsyncPostBackControl方法。

以编程方式进行更新

  Update方法对UpdatePanel控件内容模板中定义的子控件进行刷新。在以下两种情况下,Update方法会抛出异常:

  1. 在UpdateMode属性被设置为Always的情况下调用该方法。

  2. 在页面呈现期间或之后调用Update方法。

  在何种情况下需要调用Update方法呢?如果在异步回发期间需要某种服务器逻辑来决定UpdatePanel控件是否应该被更新,则需借助该方法。

触发器的使用

  我们可将UpdatePanel控件与一系列服务器事件相关联。如果某个已注册的事件通过回发被触发,那么这个UpdatePanel就会被更新。触发器既可以声明方式定义,也可以编程方式定义。

  以声明方式定义触发器:

  
  
< asp:UpdatePanel runat = " server " ID = " UpdatePanel1 " >
< ContentTemplate >
...
</ ContentTemplate >
< Triggers >
< asp:AsyncPostBackTrigger ControlID = " DropDownList1 " EventName = " SelectedIndexChanged " />
</ Triggers >
</ asp:UpdatePanel >

  对于每个触发器,我们要指定两方面信息:待监视控件的ID和要捕获事件的名称。注意,AsyncPostBackTrigger组件只能捕获服务器端事件。上例中,名为DropDownList的下拉列表(确保其AutoPostBack属性为true)选择更改时将刷新UpdatePanel;或页面任意控件执行回发时亦会触发更新(UpdateMode属性的默认值为Always)。

  利用UpdatePanel控件的Triggers集合,我们还可以编程方式添加触发器。该集合接受AsyncPostBackTrigger类的实例。

可更新面板内部引发的完整回发

  默认情况下,UpdatePanel中所有能够执行回发的子控件都会隐式地作为异步回发触发器。通过将ChildrenAsTriggers设置为false,便可阻止所有子控件触发面板的更新。注意,如果ChildrenAsTriggers为false,那么源自子控件的回发会以异步回发方式处理,并修改相关服务器控件的状态,但它们不会更新UpdatePanel的用户界面。

  为响应UpdatePanel控件内部控件的事件,我们可能需要执行完整的回发。这种情况下,我们可使用PostBackTrigger组件:

  
  
< asp:UpdatePanel runat ="server" ID ="UpdatePanel1" >
< ContentTemplate >
...
</ ContentTemplate >
< Triggers >
< asp:AsyncPostBackTrigger ControlID ="DropDownList1" EventName ="SelectedIndexChanged" />
< asp:PostBackTrigger ControlID ="Button1" />
</ Triggers >
</ asp:UpdatePanel >

  上面代码中面板同时具有同步和异步的回发触发器。当用户更改DropDownList1的选项后,将执行UpdatePanel异步更新;当用户单击Button1按钮后,整个宿主页面会被刷新。

  PostBackTrigger指定的触发器必须是受影响的UpdatePanel的子控件。

  PostBackTrigger对象不支持EventName属性。ASP.NET运行库会通过控件的IPostBackEventHandler接口来确定要为触发器的哪个服务器事件触发更新。

为用户提供返回

  部分呈现操作仍然需要回发,也仍然需要上传和下载视图状态,并在服务器端建立页面的生命周期。异步回发的优势在于,这样不需要刷新整个页面,返回给客户端的HTML标记数据更小。

UpdateProgress控件

  UpdateProgress控件用于在UpdatePanel控件执行更新的过程中提供某种反馈。

  ASP.NET AJAX框架自动控制着UpdateProgress控件的显示与隐藏,而无需手动管理。该控件的主要属性见下表:

ASP.NET 3.5核心编程学习笔记(54):UpdatePanel_第2张图片

  UpdateProgress可与特定UpdatePanel控件绑定。为此,设置字符串属性AssociatedUpdatePanelID即可。如果不指定相关联的UpdatePanel控件,那么该控件会为页面中所有UpdatePanel显示。UpdatePanel的HTML标记会在宿主页面生成时插入到页面中。

  我们通过查看页面的源文件可发现UpdateProgress控件的CSS属性display被设为none,也就是说,该控件最初是被隐藏的。如果我们要保留UpdateProgress控件的空间,在不发生更新操作时所占区域为空白,只需将其DynamicLayout属性设置为false即可。

进度条界面设计

  ASP.NET AJAX框架会在等待面板更新的进程中显示ProgressTemplate属性的内容。我们即可以声明方式指定该模板,也可以编程方式指定。对于后一种情况,我们可将任何实现ITemplate接口的对象赋给该属性。对于第一种情况,我们只需以声明方式指定进度控件的标记即可:

  
  
< asp:UpdateProgress runat ="server" ID ="UpdateProgress1" >
< ProgressTemplate >
...
</ ProgressTemplate >
</ asp:UpdateProgress >

  我们可在进度模板中添加任何控件。一般我们只向其中添加一些文件和一个GIF进度条动画。

为实现更丰富反馈而采用的客户端事件

  每次异步回发都通过客户端脚本触发。整个操作由PageRequestManager客户端对象控制,该对象在内部会调用XMLHttpRequest对象。

  Sys.WebForms.PageRequestManager对象提供了几个事件,这样,我们便能对请求和响应的处理进行自定义了:

ASP.NET 3.5核心编程学习笔记(54):UpdatePanel_第3张图片

  为注册事件处理程序,我们可像下面这样编写JavaScript代码:

  
  
var manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);

// 事件处理函数
function OnBeginRequest(sender, args)
{
...
}

  initialzeRequest事件是异步请求客户端生命周期的第一个事件。生命周期起始于请求被UpdatePanel客户端基础结构捕获的那一刻。我们可使用该事件来追溯回发源,并执行某些额外的操作。其事件数据结构为InitializeRequestEventArgs类。该类带有3个属性postBackElement、request和cancel。

  postBackElement属性存储的是DomElement对象,用于指示启动回发的DOM元素。request属性包含Sys.Net.WebRequest类型对象,代表外发请求。cancel属性用于在发送请求前撤销它。

  在调用initializeRequest处理程序后,PageRequestManager对象会立即取消所有挂起的异步请求,随后,它会引发beginRequest事件并发送数据包。

  当响应到达后,PageRequestManager对象首先会处理返回的数据并分离各隐藏字段、各UpdatePanel及所有从服务器返回的信息。一旦数据处理完毕,PageRequestManager对象便会引发客户端的pageLoading事件。该事件在收到服务器响应后,页面内容被更新前触发。我们可通过该事件来对将要更新的内容添加自定义的变换效果,或为UpdatePanel的下次更新执行某些清理工作。该事件的数据包装在PageLoadingEventArgs类的实例中。该类带有3个属性:panelsUpdating、panelsDeleting和dataItems。前两个属性为数组,分别列出了待更新和待删除的UpdatePanel。

  pageLoaded事件在页面的所有内容被刷新后触发。我们可通过该事件来提供对已更新内容的自定义变换效果(如闪烁或高亮效果)。该事件数据包装在PageLoadedEventArgs中,它带有3个属性:panelsUpdated、panelsDeleted和dataItems。前两个属性是数据,分别列出刚更新和删除的UpdatePanel。

  endRequest事件代表着异步请求的结束。不管异步回发成功与否,我们都会收到该事件。

在更新期间禁用可视元素

  如果希望防止用户在部分页面更新的过程中产生更多的输入,我们可考虑禁用用户界面。为此,我们可注册并编写beginRequest和endRequest事件的处理程序:

  
  
< script type = " text/javascript " >
function pageLoad()
{
var manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);
manager.add_endRequest(OnEndRequest);
}

// 全局变量,存放引起回发的DOM元素
var currentPostBackElem;
function OnBeginRequest(sender, args)
{
// 获取引起回发的DOM元素
currentPostBackElem = args.get_postBackElement();
if ( typeof (currentPostBackElem) == " undefined " )
return ;
// 检查引起回发的是否为btnStartTask按钮
if (currentPostBackElem.id.toLowerCase() == " btnStartTask " )
{
// 禁用按钮
$get( " btnStartTask " ).disabled = true ;
}
}

function OnEndRequest(sender, args)
{
if ( typeof (currentPostBackElem) == " undefined " )
return ;
// 恢复被禁用的按钮
if (currentPostBackElem.id.toLowerCase() == " btnStartTask " )
{
$get(
" btnStartTask " ).disabled = false ;
}
}

  endRequest事件将EndRequestEventArgs对象传给各处理程序,下表列出了该类的属性:

ASP.NET 3.5核心编程学习笔记(54):UpdatePanel_第4张图片

  注意,HTML属性disabled仅对INPUT元素有效,而不适用于超链接(<a>标签)。如果使用LinkButton控件,则必须借助JavaScript技巧来禁用用户界面。可临时将该链接的onclick处理程序替换为返回false值的处理程序,或使用透明的DIV。

撤销挂起的更新

  如果我们要撤销挂起的更新操作,该如何进行呢?为此,我们可在进度模板中添加一个撤销按钮,可将下面的代码添加到进度模板中:

  
  
< input type = " button " onclick = " abortTask() " value = " Cancel " / >

  该按钮的onclick事件函数如下所示:

  
  
function abortTask()
{
var manager = Sys.WebForms.PageRequestManager.getInstance();
if (manager.get_IsInAsyncPostBack())
manager.abortPostBack();
}

  撤销执行的更新操作相当于关闭与服务器的连接。如果这样,不会收到任何结果,页面中的任何内容都不会被更新。然而,撤销更新是一种纯粹的客户端操作,不会影响服务器端执行的任务。如果用户发起破坏性的操作,那么客户端的“取消”按钮是无法阻止当前发生在服务器端的操作的。

并发调用问题

  UpdatePanel不支持并发的异步回发,也就是说,同一时刻不允许执行两个或两个以上的异步回发。这种发往Web服务器的请求与常规ASP.NET请求不同,它带有额外的HTTP标头。该请求会传入回发表单的内容,其中包含存储视图状态的隐藏字段。其响应并不是纯粹的HTML,而是代表一种文本记录,其中每个字段描述的是页面元素的新状态。

  不难看出,部分呈现的底层模型仍旧是传统ASP.NET页面的模型。这是一种准动态模型--用户执行回发,等待片刻,然后接收到新页面。

  从技术上看,不能同时执行多个异步回发的主要原因是视图状态信息的存在。若发送两个请求,二者都会发送同一视图状态的副本,但理应返回不同的视图状态。那么,页面应该采用哪一个呢?

  出现异步回发请求时,PageRequestManager类会检查是否有另一个挂起的操作。在默认情况下,如果有,则隐式将其撤销,并执行新的操作。这样的话,我们在每个异步回发时总要修改用户界面,以确保用户在第一个操作终止前不能发起第二个。否则,第一个操作会因后续操作的出现而被撤销。

你可能感兴趣的:(asp.net)