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

在 ASP.NET 1.x 中实现脚本回调框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (54投票s)

2004 年 8 月 3 日

8分钟阅读

viewsIcon

589488

downloadIcon

4213

它允许从客户端脚本代码调用服务器事件,而无需进行页面回发和刷新。

Sample Image

引言

每个 ASP.NET 1.x Web 开发者都面临的挑战之一是,没有原生支持脚本回调到服务器而无需当前页面的回发。例如,在不强制刷新整个页面的情况下,从下拉列表中检索并显示所选员工的详细信息,这对于 UI 丰富的页面来说是一项昂贵的操作。那么,我们该如何处理呢?

感谢 ASP.NET 团队,他们在 ASP.NET 2.0 中内置了此功能,允许从客户端代码调用服务器事件,而无需进行页面回发和刷新。这很棒,但我们需要考虑 2 个事实:时间和金钱。就时间而言,我们必须等到 ASP.NET 2.0 发布,在此期间我们无能为力。就金钱而言,如果我们离不开 Visual Studio.NET IDE,我们可能被迫将 Visual Studio .NET IDE 从 2002/2003 升级到 2005。更重要的是,在 ASP.NET 1.x 中实现脚本回调根本不难。那么,我们还在等什么?

得益于 Page Controller 框架的可扩展架构,它为开发人员提供了灵活的伸缩功能,在原生支持不足的情况下。在本文中,我将引导您完成实现脚本回调框架的过程,并提供一个您可以用于 ASP.NET 1.x 的简化但有效的框架。

目标

在 ASP.NET 1.x 中实现脚本回调框架设定了几个目标,它们是

  • 与 ASP.NET 2.0 中提供的相同功能共存 - 避免在我们的实现中使用相似的函数或变量名。
  • 提供与 ASP.NET 一致的编程模型 - 类似于 `Page.GetPostBackEventReference` 函数的概念。
  • 模拟 ASP.NET 2.0 中实现的尽可能多的脚本回调框架功能。
  • 提供同步和异步回调。

背景

脚本回调框架由 2 个核心组件组成,即 `PageTemplate.cs` 和 `ScriptCallback.js` 文件。它们分别负责服务器端和客户端的回调处理。回调处理涉及的步骤如下所示

  1. 脚本准备 - 生成回调 JavaScript 代码并将其附加到控件的事件。
  2. 脚本回调 - 调用嵌入的 JavaScript,当控件的事件被触发时,该 JavaScript 会打开 HTTP 连接到指定的远程 ASP.NET 页面。
  3. 服务器响应 - 查找并调用相应的控件以做出相应响应。
  4. 回调处理 - 根据服务器响应返回的状态码调用客户端回调处理程序或错误处理程序。

脚本准备

``PageTemplate`` 继承自 ``System.Web.UI.Page``,它封装了大多数处理逻辑,并在脚本回调框架中起着至关重要的作用。它提供了 ``GetAsyncCallbackEventReference`` 和 ``GetSyncCallbackEventReference`` 函数,用于生成要附加到控件事件的 JavaScript 代码。

/// <summary>

/// Obtains a reference to a client-side script function

/// that causes, when invoked, 

/// the server to callback asynchronously to the page. 

/// This method also passes a parameter to the server control

/// that performs the callback processing on the server. 

/// </summary>

/// <param name="control">The server control to process the callback.

///    It must implement the IClientCallbackEventHandler</param>

/// <param name="args">Argument to be passed to the server control

///    that performs the callback processing on the server</param>

/// <param name="cbHandler">Callback Handler (JavaScript function),

///    invokes when callback operation completed successfully</param>

/// <param name="context">Any DHTML reference or extra information

///    to pass to the Callback or Error Handler</param>

/// <param name="errHandler">Error Handler (JavaScript function),

///    invokes when error occurred during the callback operation</param>

/// <returns>Asynchronous callback JavaScript code</returns>

public string GetAsyncCallbackEventReference(
     System.Web.UI.Control control,
     string args,
     string cbHandler,
     string context,
     string errHandler)
/// <summary>

/// Obtains a reference to a client-side script function that causes, 

/// when invoked, the server to callback synchronously to the page. 

/// This method also passes a parameter to the server control

/// that performs the callback processing on the server. 

/// </summary>

/// <param name="control">The server control to process

///    the callback. It must implement the IClientCallbackEventHandler</param>

/// <param name="args">Argument to be passed to the server

///    control that performs the callback processing on the server</param>

/// <param name="cbHandler">Callback Handler (JavaScript function),

///    invokes when callback operation completed successfully</param>

/// <param name="context">Any DHTML reference or extra information

///    to pass to the Callback or Error Handler</param>

/// <param name="errHandler">Error Handler (JavaScript function, 

///    invokes when error occurred during the callback operation</param>

/// <returns>Synchronous callback JavaScript code</returns>

public string GetSyncCallbackEventReference(
    System.Web.UI.Control control,
    string args,
    string cbHandler,
    string context,
    string errHandler)

例如,以下对 ``GetAsyncCallbackEventReference`` 函数的调用将生成回调 JavaScript 代码并将其附加到 _IncreaseButton_ 的 ``onclick`` 事件。

服务器端代码

// Callback asynchronously when the button is clicked

IncreaseButton.Attributes["onclick"] = GetAsyncCallbackEventReference
(
Form1,
String.Format("document.getElementById('{0}').value", txtValue.UniqueID),
"IncreaseValueHandler",
String.Format("document.getElementById('{0}')", txtValue.UniqueID),
null
);

客户端代码

<input name="IncreaseButton" id="IncreaseButton" 
 type="button" value="Increase Value"
 onclick="javascript:WebForm_DoAsyncCallback('Form1',
 document.getElementById('txtValue').value,
 IncreaseValueHandler,
 document.getElementById('txtValue'),
 null);" />

``PageTemplate`` 重写了 ``System.Web.UI.Page`` 类的 ``Render`` 方法,以确保 ``ScriptCallback.js`` 文件以及执行回调所需的所有脚本代码在呈现给客户端之前正确地在页面或控件中引用。 ``ScriptCallback.js`` 文件包含客户端代码,用于初始化回调基础结构,打开到指定远程 ASP.NET 页面的 HTTP 连接以进行回调处理,以及在收到服务器结果后调用相应的回调处理程序或错误处理程序。

脚本回调

每当控件的事件被触发时,其嵌入的 JavaScript 代码就会调用 ``ScriptCallback.js`` 文件中的 ``WebForm_DoAsyncCallback`` 或 ``WebForm_DoSyncCallback`` 以回调到指定的远程 ASP.NET 页面进行处理。在内部,它使用 COM 对象发出 HTTP POST 或 GET 命令到指定的目标 URL。此 COM 对象随 Internet Explorer v5.0 或更高版本提供,许多开发人员对此并不陌生。

var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");

HTTP 动词是 GET 或 POST,具体取决于要发送的数据的大小。如果大小超过 2KB,则使用 POST 命令。HTTP 请求包含三个逻辑元素:`__SCRIPTCALLBACKID`、`__SCRIPTCALLBACKPARAM` 和已发布的数据。`__SCRIPTCALLBACKID` 值包含要调用的控件的 ID(事件目标参数),而 `__SCRIPTCALLBACKPARAM` 携带服务器端存根方法的输入参数。

服务器响应

``PageTemplate`` 重写了 ``System.Web.UI.Page`` 类的 ``OnInit`` 方法,以确定当前请求是 Postback 还是 Callback 模式。它在 ``Request`` 集合中查找 ``__SCRIPTCALLBACKID`` 条目。如果是,它会将公共 ``IsCallback`` 属性设置为 true,并得出结论这是一个回调调用。在 ``OnLoad`` 方法中,它首先调用 ``base.OnLoad`` 方法以确保包括动态创建的控件在内的所有控件都已正确初始化。如果是一个回调,它会调用 ``HandleClientCallback`` 函数来进一步处理请求。

在内部,它使用从 ``__SCRIPTCALLBACKID`` 条目中获得的值,即要调用的控件的 ID。它在 ``Page`` 的 ``Controls`` 集合中查找指定的控件,并检查引用的控件是否实现了 ``IClientCallbackEventHandler`` 接口(可能是页面本身)。如果 ``Control`` 实现了所需的接口,``PageTemplate`` 就会调用接口上的 ``RaiseClientCallbackEvent`` 方法,并根据调用结果准备响应。

``IClientCallbackEventHandler`` 接口只有一个方法,其签名如下

string RaiseClientCallbackEvent(string eventArgument);

该方法的 ``eventArgument`` 参数从已发布值的 ``Request`` 集合中检索。输入数据的字符串表示形式包含在名为 ``__SCRIPTCALLBACKPARAM`` 的条目中。此字符串可以是您想要的任何内容,包括数字、日期、逗号分隔值、XML 数据或 Base64 数据、JavaScript 等。

``PageTemplate`` 使用不同的状态码来通知客户端脚本代码服务器端回调处理的状态。此处使用的状态码与标准 HTTP ``STATUS_CODE`` 使用的状态码含义不同。下表列出了 4 种可能的状态码及其含义

STATUS 描述
200 好的
404 找不到指定的控件
500 内部错误(未知错误)
501 指定的控件未实现 ``IClientCallbackEventHandler`` 接口

注意:状态码附加到名为 “`__SCRIPTCALLBACKSTATUS`” 的自定义 HTTP 标头条目中。

回调处理

回调操作完成后,将通知客户端脚本检查名为 “`__SCRIPTCALLBACKSTATUS`” 的自定义 HTTP 标头条目返回的状态码。如果状态码为 “200”,则调用已注册的回调处理程序。否则,将调用已注册的错误处理程序来完成工作。

使用代码

有了脚本回调框架,开发一个利用脚本回调的页面就像执行以下几个步骤一样简单

  1. 在任何打算支持回调的控件上实现 ``IClientCallbackEventHandler`` 接口。控件可以是 ``Page``、``UserControl`` 以及任何服务器控件。
  2. 在 ``RaiseClientCallbackEvent`` 方法中编写将从客户端调用的服务器端代码。执行此操作时,您需要定义调用的数据模型,并决定要交换什么信息以及以什么格式进行交换。实际交换的数据必须是字符串,但字符串的内容可以是您想要的任何内容,包括数字、日期、逗号分隔值、XML 数据或 Base64 数据、JavaScript 等。
  3. 为每个实现 ``IClientCallbackEventHandler`` 接口的控件分配一个唯一的 ID,无论是声明性地 (HTML) 还是以编程方式 (代码)。
  4. 将 ``GetAsyncCallbackEventReference`` 或 ``GetSyncCallbackEventReference`` 函数发出的 JavaScript 代码附加到控件的事件。调用时,异步或同步地发起和处理回调操作。请参阅 _限制_ 部分,如果 ``System.Web.UI.Page`` 或其派生类用作回调目标。

    局限性

    ``System.Web.UI.Page`` 或其派生类不能用作 ``GetAsyncCallbackEventReference`` 或 ``GetSyncCallbackEventReference`` 函数的第一个输入参数,因为系统无法找出它们的 ID。变通方法是,您应该声明一个 ``System.Web.UI.HtmlControls.HtmlForm`` 类型的变量,并且其名称必须与页面 HTML 源中使用的 ``Form`` 元素的 ``ID`` 匹配,然后将此变量用作上述函数的第一个输入参数。

    HTML
    <form id="Form1" method="post" runat="server"></form>
    代码隐藏
    protected System.Web.UI.HtmlControls.HtmlForm Form1;
    
    IncreaseButton.Attributes["onclick"] = GetSyncCallbackEventReference
    (
        Form1,
        String.Format("document.getElementById('{0}').value", 
        txtValue.UniqueID), "IncreaseValueHandler",
        String.Format("document.getElementById('{0}')", 
        txtValue.UniqueID), null
    );
  5. 在页面的 HTML 源中或在页面引用的任何脚本文件中编写 _回调处理程序_ 或 _错误处理程序_ JavaScript 函数。

简而言之,您可以使用回调来更新页面上的单个元素,例如 ``Label`` 或 ``Panel``,提供相同数据的不同视图,按需下载其他信息,或自动填充一个或多个字段。要将服务器端生成的视图与现有页面合并,通常使用页面的 DHTML 对象模型。为每个可更新的 HTML 标签提供一个唯一的 ID,并使用 ``innerHTML`` 属性或 DOM 提供的任何其他属性和方法来修改其内容。

最后但同样重要的是,我包含了一个示例,可从本文顶部的链接获取,该示例演示了脚本回调框架的功能。

关注点

在对脚本回调框架进行的无数次测试中,我发现 ``Microsoft.XMLHTTP`` COM 对象会缓存每次调用结果的 URL 和帖子数据。如果之前使用过相同的请求 URL 和帖子数据,则不会打开到请求 URL 的连接。但是,缓存项的数量对我来说是未知的。

结论

脚本回调允许您回调到服务器而不刷新整个页面,这让用户产生一切都在客户端发生的错觉。再次,我介绍了脚本回调框架,它是将在 ASP.NET 2.0 发布时完全构建的代码的简化版本。您也可以在今天获得所需的功能,并且明天在理解 ASP.NET 2.0 中的类似概念方面将领先一步。

© . All rights reserved.