异步加载和显示页面内容,并支持完全回发
一个具有更少通信开销和更高性能的 AJAX UpdatePanel。

引言
我对 Acoustic 的文章《用户控件的增量页面显示模式》[^] 非常感兴趣。他构建了一个用于部分内容真正异步加载的解决方案。他的解决方案很好,但有一个缺点:他无法响应回发。因此,控件是静态的。它们只能显示初始内容,用户无法与控件进行交互。我决定编写一个全新的控件,可以支持以下功能:
- 开发人员甚至无需编写一行 JavaScript。
- 真正异步加载用户控件(*.ascx)。
- 用户应该能够与用户控件进行交互。
- 更新时无需渲染整个页面,就像 AJAX
UpdatePanel
所做的那样。为了节省服务器资源,只需渲染用户控件。 - 应该只有一个小的通信开销。这意味着只有属于用户控件的
viewstate
的一部分才会被传输回服务器。AJAXUpdatePanel
会将整个页面以及所有控件的viewstate
传输回服务器,这可能是一个很大的开销。 - 该控件应支持一些小功能,例如每隔 n 毫秒自动更新。
使用场景
您可以使用 PartialUpdatePanel
而不是 AJAX UpdatePanel
的示例场景可能包括:
- 页面中需要回发支持但不需要整个页面环境信息的独立区域(例如,具有分页功能的数据列表,用户可以浏览新闻、信息流、邮件等)。
- 当您的控件需要完成长时间操作时,向用户提供反馈。在这种情况下,使用渲染方法为“
Clientside
”的PartialUpdatePanel
。周围的页面将显示带有等待消息。用户将获得反馈,知道正在进行某项操作,需要等待。
基本概念
整个控件包括服务器端控件、客户端脚本部分和一个 HttpHandler
。ASCX 控件的渲染场景在下图中有描述。
-
ASPX
Page
包含一个PartialUpdatePanel
,其UserControlPath
属性设置为 ASCX 控件。<iucon:PartialUpdatePanel runat="server" ID="Panel1" UserControlPath="~/PostBackSample.ascx"> <ErrorTemplate> Unable to refresh content </ErrorTemplate> <LoadingTemplate> <div style="margin-left: 84px; margin-top: 10px;"> <asp:Image ID="Image1" runat="server" ImageUrl="~/images/loading.gif" /> </div> <div style="text-align: center"> Updating... </div> </LoadingTemplate> </iucon:PartialUpdatePanel>
-
页面加载时,一些 JavaScript 调用
PartialUpdatePanelHandler
。它通过 HTTP-POST 发送用户控件的路径以及其他数据,例如控件的viewstate
。// create request var request = new Sys.Net.WebRequest(); request.set_url('PartialUpdatePanelLoader.ashx'); request.set_httpVerb('POST'); request.set_body(this._createRequestBody(eventTarget, eventArgument)); request.set_userContext(this); request.add_completed(this._loadingComplete);
-
用户控件需要一个
Page
对象来进行渲染。这项工作由PanelHostPage
完成。// this code is part of PartialUpdatePanelHandler if (context.Request.Form["__USERCONTROLPATH"] != null) { PanelHostPage page = new PanelHostPage( context.Request.Form["__USERCONTROLPATH"], context.Request.Form["__CONTROLCLIENTID"]); ((IHttpHandler)page).ProcessRequest(context); context.Response.Clear(); context.Response.Write(page.GetHtmlContent()); }
-
用户控件通常通过将其添加到
Page
的Controls
集合中来渲染。protected override void CreateChildControls() { // Load Control if (_controlPath != null) _mainForm.Controls.Add(LoadControl(ResolveUrl(_controlPath))); base.CreateChildControls(); }
-
页面输出被发送回
HttpHandler
。 -
内容被传输到客户端,并通过 DOM 操作插入到活动的 HTML 文档中。
contentPanel.innerHTML = sender.get_responseData();
就是这样。
视图状态(ViewState)
PartialUpdatePanel
的内容被像整个页面一样处理。因此,它有自己的 viewstate
,存储在一个隐藏字段中。隐藏字段被传输到 HttpHandler
并由 PanelHostPage
加载。加载、序列化和反序列化 viewstate
的逻辑在 PanelHostPage
的两个重写方法中完成。
private string _pageViewState;
protected override object LoadPageStateFromPersistenceMedium()
{
PartialPageStatePersister persister =
PageStatePersister as PartialPageStatePersister;
persister.PageState = _pageViewState;
persister.Load();
return new Pair(persister.ControlState, persister.ViewState);
}
protected override void SavePageStateToPersistenceMedium(object state)
{
PartialPageStatePersister pageStatePersister =
this.PageStatePersister as PartialPageStatePersister;
if (state is Pair)
{
Pair pair = (Pair)state;
pageStatePersister.ControlState = pair.First;
pageStatePersister.ViewState = pair.Second;
}
else
{
pageStatePersister.ViewState = state;
}
pageStatePersister.Save();
_pageViewState = HttpUtility.UrlEncode(pageStatePersister.PageState);
}
自定义 PageStatePersister
PartialPageStatePersister
使用 LosFormatter
正确加载和存储 ViewState
和 ControlState
。
_pageViewState
的内容再次以隐藏输入字段的形式传输回客户端。
事件处理
在 ASP.NET 中,事件处理非常简单。例如,当用户单击按钮时,该按钮的 ClientID
会在 __EVENTTARGET
字段中传回页面。这个行为很容易在我们这里重现。我们所要做的就是为每个按钮添加 OnClientClick
调用,从那里调用一个加载部分内容的方法,并将事件源添加到传递给我们的 HttpHandler
的 __EVENTTARGET
字段中。我们除此之外别无他事,其余的都交给 ASP.NET 事件管道处理。
使用控件
在使用该控件之前,您必须在 web.config 中注册 HttpHandler
。
<httpHandlers>
<add verb="*" path="*.ashx" validate="false"
type="iucon.web.Controls.PartialUpdatePanelHandler"/>
</httpHandlers>
还应在 appSettings
中添加一个值。请将该值更改为您自己的值!
<appSettings>
<add key="PartialUpdatePanel.EncryptionKey" value="k39#9sn1"/>
</appSettings>
然后,只需在页面中添加一个 PartialUpdatePanel
并设置 UserControlPath
属性。在 LoadingTemplate
和 ErrorTemplate
中添加一些控件。当更新操作正在进行时,LoadingTemplate
的内容将变得可见。如果在更新过程中发生错误(例如,服务器超时),则显示 ErrorTemplate
的内容。如果将 AutoRefreshInterval
设置为大于 0
的值,面板将每 n 毫秒自动刷新一次。
还有一种机制可以通过 JavaScript 或服务器端代码与该控件进行通信。Parameters
是一个接受键值对的集合。当控件被渲染时,它们通过 HTTP-POST 传输到控件。以下示例显示了如何在 ASPX 页面中提供一些参数,通过 JavaScript 设置值,并在 ASCX 控件中借助 ParameterCollection
类读取它们。
<%-- PartialUpdatePanel with named parameters --%>
<iucon:PartialUpdatePanel runat="server" ID="PartialUpdatePanel4"
UserControlPath="~/ParameterSample.ascx">
<Parameters>
<iucon:Parameter Name="MyParameter" Value="Hello world" />
<iucon:Parameter Name="Counter" Value="0" />
</Parameters>
<ErrorTemplate>
Unable to refresh content
</ErrorTemplate>
</iucon:PartialUpdatePanel>
<%-- Change the value of the parameter "Counter" and refresh the panel --%>
<script type="text/javascript">
var counter = 0;
function updateParameterSample()
{
$find("PartialUpdatePanel4").get_Parameters()["Counter"] = ++counter;
$find("PartialUpdatePanel4").refresh();
}
</script>
<input type="button" onclick="updateParameterSample(); return false;"
value="Click to update panel with counter" />
ASCX 的代码隐藏从 ParameterCollection
读取参数值。
public partial class ParameterSample : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
iucon.web.Controls.ParameterCollection parameters =
new iucon.web.Controls.ParameterCollection.Instance;
Label1.Text = "Called " + parameters["Counter"] + " times";
}
}
您也可以更改参数的值。它们将被传输回来并通过 JavaScript 访问。
InitialRenderBehaviour
属性控制控件是在正常页面渲染期间由服务器渲染(InitialRenderBehaviour.Serverside
)。如果此属性设置为 InitialRenderBehaviour.Clientside
,则当页面已传输到浏览器时,客户端会向服务器发送请求以渲染控件。这对于用户来说很有用,如果页面的一部分需要更长的时间来渲染,但用户可以先看到其他部分。他无需等待整个页面,而是可以在其他部分加载时与某些部分进行交互。InitialRenderBehaviour.None
会导致控件在通过 JScript 调用 $find("PartialUpdatePanel4").refresh();
请求之前不进行渲染,如以上示例之一所示。在这种情况下,<InitialTemplate>
的内容会显示,直到发生刷新调用。
<iucon:PartialUpdatePanel runat="server" ID="PartialUpdatePanel1"
UserControlPath="~/ExternalRefreshSample.ascx"
InitialRenderBehaviour="None">
<ErrorTemplate>
Unable to refresh content
</ErrorTemplate>
<LoadingTemplate>
Updating...
</LoadingTemplate>
<InitialTemplate>
Nothing useful here until refresh() gets called
</InitialTemplate>
</iucon:PartialUpdatePanel>
如果您想防止 LoadingTemplate
和 ContentTemplate
出现闪烁,您可以使用 DisplayLoadingAfter
属性设置一个以毫秒为单位的时间间隔。在超时之前,不会显示加载模板。如果您的 PartialUpdatePanel
刷新速度非常快,这很有用。
通过使用 RenderAfterPanel
属性,您可以按顺序加载内容。如果您的站点使用了例如 10 个 PartialUpdatePanel
并且 InitialRenderBehaviour
设置为 Clientside
,您将同时向 Web 服务器发出 10 个请求。这可能会给服务器带来很大的负载。RenderAfterPanel
意味着在另一个加载完成后,当前 PartialUpdatePanel
不会被刷新。因此,内容是逐步加载的,服务器的负载会降低。
您可以从您的 UserControl
动态添加和运行 JavaScript 代码。只要将 addScriptTags
设置为 true
,就可以支持 ScriptManager.RegisterStartupScript
和 ScriptManager.RegisterClientScriptBlock
方法。以下代码显示了在托管在 PartialUpdatePanel
中的 UserControl
的按钮被单击时如何显示一个警告框。
protected void Button1_Click(object sender, EventArgs e)
{
string script = string.Format("alert('{0}');", TextBox1.Text);
ScriptManager.RegisterStartupScript(this, GetType(), "alert", script, true);
}
JavaScript 代码的内部传输和执行有点棘手。您不能简单地在 HTML 代码中添加一个 <script>
标签。然后,当 HTML 代码更新您文档的 DOM 树时,<script>
节点不会被执行。在内部,PartialUpdatePanel
将所有 JavaScript 片段传输到一个隐藏的 div
中。该 div
由 ScriptRenderer
类创建。更新文档中新渲染内容的 PartialUpdatePanel
JavaScript 代码读取 div
,使用 Sys._ScriptLoader.getInstance()
来执行传输的 JavaScript,最后,通过 DOM 操作从文档中删除 div
。
属性
EncryptUserControlPath |
使用 DES 算法和 web.config 中定义的值加密您的 UserControl 的路径。此属性默认设置为 true 。注意:如果设置了 EncryptUserControlPath ,则无法通过 JavaScript 更改 UserControlPath 。 |
UserControlPath |
您的 UserControl 的虚拟路径。 |
AutoRefreshInterval |
强制面板每 n 毫秒刷新一次。 |
DisplayLoadingAfter |
在一个成功加载后渲染当前 UserControl 。 |
参数 |
本文中已描述 ;-) |
InitialRenderBehaviour |
设置初始渲染模式为 Serverside、Clientside 或 None。 |
AJAX 控件工具包支持
让 AJAX 控件工具包中的一些控件与 PartialUpdatePanel
一起工作是一项非常艰巨的任务。主要问题在于控件在运行时通过调用 $create()
在 Sys.Application.add_init
中实例化自身。
Sys.Application.add_init(function() {
$create(AjaxControlToolkit.AutoCompleteBehavior, {...}, null, null, $get(
"PartialUpdatePanel7_myTextBox"));
);
现在,当部分回发完成时,相同的代码会再次运行,并且 $create
调用 Sys.Application.addComponent
函数会失败,因为组件已经被实例化了。
为了解决这个问题,我不得不进行一些非常粗糙的修改。在执行部分更新响应中的客户端脚本之前,我将 Sys.Application.addComponent
的函数引用更改为一个本地函数,该函数会检查组件是否已经被实例化。
_addComponent : function(component) {
var id = component.get_id();
if (typeof(Sys.Application._components[id]) === 'undefined')
Sys.Application._components[id] = component;
}
这可以防止 addComponent
失败并抛出异常。当所有脚本成功运行后,原始函数指针会被恢复。
最后的话
如果您使用此控件,如果您能将您的项目 URL 发送给我,我将非常高兴,这样我就可以看到该控件的实际应用。还有……请不要忘记为这篇文章投票。
历史与更新
2010 年 9 月 13 日 |
1.8 版
|
2008 年 11 月 14 日 |
1.6 版 特别感谢 CodePlex 的 grown 为PartialUpdatePanel 所做的出色工作!
|
2008 年 7 月 9 日 |
版本 1.5.2
|
2008 年 6 月 10 日 |
版本 1.5
|
2008 年 5 月 20 日 |
版本 1.4
|
2008 年 3 月 29 日 |
版本 1.3
|
2008 年 2 月 28 日 |
版本 1.2.1
|
2008 年 2 月 27 日 |
版本 1.2
|
2008 年 2 月 20 日 | 首次发布 |
该项目托管在 CodePlex [^] 上。那里的更新间隔更短。因此,如果您对此项目感兴趣,应定期访问该网站。
已知问题
ModalPopupExtender
仅在 FireFox 中运行良好。Internet Explorer 有一些我尚未解决的问题。因此,如果您想从 PartialUpdatePanel
中显示模态弹出窗口,请在父页面中创建 ModalPopupExtender。要显示对话框,请在 PartialUpdatePanel
UserControl
的代码隐藏中调用 ScriptManager.RegisterStartupScript
。
示例
<script type="text/javascript">
function showModalPopupViaClient() {
var modalPopupBehavior = $find('programmaticModalPopupBehavior');
modalPopupBehavior.show();
}
function hideModalPopupViaClient() {
var modalPopupBehavior = $find('programmaticModalPopupBehavior');
modalPopupBehavior.hide();
}
</script>
<asp:Button runat="server" ID="hiddenTargetControlForModalPopup" style="display:none"/>
<ajaxToolkit:ModalPopupExtender runat="server" ID="programmaticModalPopup"
BehaviorID="programmaticModalPopupBehavior"
TargetControlID="hiddenTargetControlForModalPopup"
PopupControlID="programmaticPopup"
BackgroundCssClass="modalBackground"
DropShadow="True"
PopupDragHandleControlID="programmaticPopupDragHandle"
RepositionMode="RepositionOnWindowScroll" >
</ajaxToolkit:ModalPopupExtender>
<asp:Panel runat="server" CssClass="modalPopup" ID="programmaticPopup"
style="display:none;width:350px;padding:10px">
<asp:Panel runat="Server" ID="programmaticPopupDragHandle"
Style="cursor: move;background-color:#DDDDDD;border:solid 1px Gray;
color:Black;text-align:center;">
ModalPopup shown and hidden in code
</asp:Panel>
<iucon:PartialUpdatePanel runat="server" ID="PartialUpdatePanel4"
UserControlPath="~/ToolkitSample.ascx" />
You can now use this sample to see how to use ModalPopup with an invisible TargetControl.
The ModalPopupExtender
this popup is attached to has a hidden target control. The popup is hidden
<asp:LinkButton runat="server" ID="hideModalPopupViaServer" Text="on the server side
in code behind" OnClick="hideModalPopupViaServer_Click" /> and
<a id="hideModalPopupViaClientButton" href="#">on the client in script</a>.
<br />
</asp:Panel>
您的 PartialUpdatePanel
UserControl
中的代码隐藏将如下所示:
public partial class ToolkitSample : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
ScriptManager.RegisterStartupScript(this, GetType(), "showPopup",
"showModalPopupViaClient()", true);
}
}
这将执行 showPopup
,然后在部分 PostBack 后显示模态弹出窗口。