动态用户控件,让您的控件支持 Ajax






4.89/5 (31投票s)
2007年10月8日
6分钟阅读

141552

1351
无需完全重新加载页面即可更新 UpdatePanel。
- 下载演示项目 - 334.5 KB (需要 VS 2005 SP1)
- 下载源码 - 17.2 KB
引言
本文介绍了一个可以托管 UserControl
的控件。它允许在不修改任何代码的情况下,使 UserControl 支持 Ajax。主要的好处是,当刷新其内容时,它不会实例化整个页面,而只会实例化包含的用户控件。
大多数 ASP.NET 功能(至少是我测试过的)都得到了支持:ViewState、ControlState、Postback 事件、验证器...此外,此控件的一个很酷的功能是允许 UserControl
进行跨域 Ajax Postback。
背景
当我想在我的 ASP.NET 项目中添加一些 Ajax 功能时,我首先查看了 Microsoft Ajax Toolkit。然而,UpdatePanel
的一个缺点是,当它刷新其内容时,它不仅实例化 UpdatePanel
,还实例化整个页面。在某些页面部分相互交互的情况下,这很棒。然而,当您的 UpdatePanel
与主内容独立(例如,一个菜单)并且您的页面很重时,其副作用是刷新页面的一个小部分需要花费很长时间。
我本可以使用一些 WebService 来更新我的内容,但我不喜欢从 C# 函数输出 HTML,而且这种方式设计内容并不容易。Microsoft 为我们提供了 UserControls,它们在设计和编码方面都很出色:所以我开始考虑一种 UpdatePanel
,它将托管一个 UserControl
并允许其在不重新加载整个页面的情况下进行刷新。
主要困难在于允许开发人员在不修改任何代码的情况下,使 UserControl
支持 Ajax。
实现
以下是该控件实现方式和工作原理的快速概述

- 在初始页面请求之后,客户端会收到一个标准的 HTML 页面
- 当客户端在
DynamicUserControl
托管的控件上触发 Postback 事件时,该事件会被一些 JavaScript 代码捕获并重定向到DUCHandler
- 在服务器端,
DUCHandler
会实例化一个只包含DynamicUserControl
和自定义UserControl
的虚拟 Page。 - 从
UserControl
的角度来看,这是一个标准的页面 Postback。 - 输出结果随后被发送回客户端,浏览器只会更新触发 Postback 的
DynamicUserControl
。 - 注意:由
DynamicUserControl
之外的任何其他 Postback 事件都由 Page handler 处理。
我将尝试解释一些我在服务器端和客户端实现此功能时使用的技巧。
服务器端
这部分的主要难点在于覆盖用于处理 ViewState、ControlState 和框架所有内部内容的机制。不幸的是,大多数 ASP.NET 类都是内部的或已密封的(或两者兼有),这使得集成变得困难。
解决方案(技巧)是使用反射 API 来访问所有这些方法和成员。使用反射的缺点是代码非常依赖于框架版本,并且在任何轻微更新时都可能中断。
DUCHandler
实际上只是 Page handler 的一个包装器:它创建了一个只包含 DynamicUserControl
(其中包含 UserControl
)的页面。然而,有一些值得注意的技巧。
DUCPageWrapper
:重写了Page
的FindControl
机制。它允许内部控件拥有与完整页面控件树中相同的 ID,而无需所有周围的控件。DUCScriptManagerWrapper
:重写了使用脚本数组(特别是验证器)的控件所需的RegisterArrayDeclaration
。DUCRedirectModule
:在将重定向响应发送到客户端之前进行拦截(即,它允许DynamicUserControl
处理Response.Redirect
方法)。
客户端
这确实是 DynamicUserControl
的核心。
脚本所做的第一件事是覆盖 __doPostBack
函数。因此,当客户端触发 Postback 事件时,代码会检查它是否源自 DynamicUserControl
内部。在这种情况下,脚本会解析该控件以查找它包含的表单输入。然后,它使用所有数据向 DUCHandler
发送 POST 请求,并用 HTML 输出替换控件内容。
我遇到过的一个问题是将 DUCHandler
输出的脚本代码和脚本包含集成到现有页面中:简单地将 HTML 标签放入 innerHTML
并不足够。我必须以 DOM 的方式创建脚本包含元素(即使用 document.createElement
)并延迟调用 window.eval
以在当前页面作用域中正确评估 JavaScript 块。(请参见 DynamicUserControl.js 文件中的 DynamicUserControl._updateContainer()
和 DynamicUserControl._scriptExecutor()
)。
此外,为了允许该控件将数据 POST 到另一个子域,我需要使用一个代理 iframe。使用 iframe 代理是绕过 IE 和 Firefox 浏览器安全限制的解决方案。您可以查看 JS 文件中的 DynamicUserControl._doPostBackXSS()
函数来了解它的工作原理,并将其与 XmlHttpRequest
的标准数据 POST 方法(在 DynamicUserControl._doPostBackXHR()
函数中)进行比较。
使用动态用户控件
为了使用此控件,您的项目必须是 ASP.NET 2.0,并且必须引用 Microsoft Ajax extensions DLL。
以下是您可以在 Web.config 文件中进行配置的内容
<pages>
<controls>
...
<add tagPrefix="jp" namespace="DUCExtension" assembly="DUCExtension"/>
</controls>
</pages>
...
<httpHandlers>
<add verb="*" path="*.duc"
type="DUCExtension.Modules.DUCHandler, DUCExtension" />
</httpHandlers>
...
<httpModules>
<add name="DUCRedirect"
type="DUCExtension.Modules.DUCRedirectModule, DUCExtension"/>
</httpModules>
如果您想启用跨域 Postback,还需要添加以下几行
<appSettings>
<add key="DUCDomain" value="mydomain.com"/>
</appSettings>
然后,如果您以这种方式在页面中使用 UserControl
<%@ Register TagPrefix="uc" TagName="Test" Src="~/UserControl/Test.ascx" %>
...
<uc:Test runat="server" ID="ucTest" />
您可以将您的代码替换为
<jp:DynamicUserControl runat="server" ID="ducTest"
UserControlPath="~/UserControl/Test.ascx"
EnablePostBackRegistration="true" >
</jp:DynamicUserControl>
您还可以添加一个 ProgressTemplate
,它将在控件更新期间显示
<jp:DynamicUserControl runat="server" ID="ducTest"
UserControlPath="~/UserControl/Test.ascx"
EnablePostBackRegistration="true" >
<ProgressTemplate>
<div>
<asp:Image runat="server" ImageUrl="~/img/ajax-loader.gif" />
</div>
</ProgressTemplate>
</jp:DynamicUserControl>
最后,这里是对可用属性的快速概述
UserControlPath
:要实例化的用户控件的路径UserControl
:获取嵌入的用户控件ProgressTemplate
:在更新控件时显示的 PContentUrlMap
:用户控件路径的前缀(用于跨域 Postback)EnablePostBackRegistration
:如果设置为true
,则嵌入的控件可以注册 PostBack 数据(即Page.RegisterRequiresPostBack
)UserControlProperties
:设置嵌入的UserControl
属性。这是一个示例
<jp:DynamicUserControl runat="server" ID="ducTest" UserControlPath="~/UserControl/Test.ascx" EnablePostBackRegistration="true" > <UserControlProperties> <jp:UserControlProperty Name="TestString" Value="Toto"/> <jp:UserControlProperty Name="TestInt" Value="123"/> <jp:UserControlProperty Name="TestDouble" Value="45.26"/> </UserControlProperties> </jp:DynamicUserControl>
(假设
TestString
、TestInt
和TestDouble
是嵌入的UserControl
的公共属性)
限制
我对其进行了大量的测试,但我确信它并非没有错误。除了您可能发现的错误之外,还有一些限制需要您注意
DynamicUserControl
内部所有控件的事件验证都被禁用,并且在某些情况下必须在页面级别禁用(即,通过在Page
指令中设置EnableEventValidation="false"
)- 您无法在页面的声明性部分为用户控件设置属性
Page
可以依赖于嵌入在 DUC 中的用户控件,但用户控件不能依赖于它所在的页面!- 使用 iframe 的副作用是它会在浏览器历史记录中创建一个条目(仅在使用跨域功能时)
- 仅在 Firefox 2、IE6 SP2 和 IE7 下进行了测试
- Validators Hack 在新的 Framework 3.5 Beta 2 中不起作用
历史
- 2007年10月8日
- 首次发布
- 2007年10月11日
- 修复了 Framework 3.5 Beta 2 (2.0.50727.1378) 的错误
- 2007年10月30日
- 增加了对动态添加的样式表文件的支持(参见此主题)
- 对
_scriptExecutor
(DynamicUserControl.js) 进行了少量修正 - 增加了对 ASP.NET 主题的支持
- 2007年10月31日
- 在 Kevin 的帮助下修复了错误(参见此主题)
- 修正了关于验证器的一些错误(尤其是在 IE 中)
- 增加了对 ScriptManager 中任何 ScriptMode 的支持
- 2007年11月1日
- 修正了动态创建控件的 ControlState 未加载的错误
- 2007年11月14日
- 修复了使用
Response.Redirect
时的错误 - 增加了对多行文本框的支持
- 增加了对用户控件属性的支持(感谢 Kevin)
- 修复了使用