65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (31投票s)

2007年10月8日

6分钟阅读

viewsIcon

141552

downloadIcon

1351

无需完全重新加载页面即可更新 UpdatePanel。

引言

本文介绍了一个可以托管 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。

实现

以下是该控件实现方式和工作原理的快速概述

Dynamic user control overview
  • 在初始页面请求之后,客户端会收到一个标准的 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:重写了 PageFindControl 机制。它允许内部控件拥有与完整页面控件树中相同的 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:在更新控件时显示的 PContent
  • UrlMap:用户控件路径的前缀(用于跨域 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>

    (假设 TestStringTestIntTestDouble 是嵌入的 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)
© . All rights reserved.