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

OutPost:通过 XmlHttp 进行 PostBack

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.49/5 (17投票s)

2005 年 10 月 6 日

CPOL

13分钟阅读

viewsIcon

273002

downloadIcon

424

无需重新加载整个页面即可实现 ASP.NET WebForms 的真实 postback。

A screenshot of The iGoogle Demo

演示项目中“iGoogle 演示”的截图

引言

本文假定您熟悉 ASP.NET WebForms。您可能还想了解 AJAX 的基础知识。以下引自 Wikipedia 关于 AJAX 的介绍

AJAX 应用程序看起来几乎就像它们托管在用户本地,而不是在服务器上的互联网上。原因在于:页面会更新,而不是完全刷新。

“所有通常会生成 HTTP 请求的用户操作都以 JavaScript 调用 AJAX 引擎的形式进行”,Jesse James Garrett 在首次定义该术语的文章中写道。“任何不需要回服务器就能响应用户操作的行为——例如简单的输入验证、在内存中编辑数据,甚至是一些导航——引擎都能自行处理。如果引擎需要从服务器获取信息才能响应——例如提交数据进行处理、加载额外的界面代码或检索新数据——引擎会异步发起这些请求,通常使用 XML,而不会中断用户与应用程序的交互。”

传统的 Web 应用程序本质上是将用户填写的表单提交给 Web 服务器。Web 服务器则通过发送一个新网页进行响应。由于服务器每次都必须提交一个新页面,因此应用程序的运行速度比本地应用程序慢且笨拙。

另一方面,AJAX 应用程序可以向 Web 服务器发送请求,只检索所需数据,通常使用 SOAP 或其他基于 XML 的 Web 服务方言。在客户端,JavaScript 处理 Web 服务器的响应。结果是更具响应性的界面,因为 Web 浏览器和 Web 服务器之间交换的数据量大大减少。Web 服务器的处理时间也得以节省,因为其中很大一部分是在客户端完成的。

AJAX 是一场维护的噩梦

今天,您必须创建同时能在 IE 和 Firefox 中工作的 Web 应用程序,这确实可行,但也很耗时,而且是一场维护的噩梦。

AJAX 之所以变得非常流行,是因为它满足了 Web 应用程序对速度和响应能力的需求,而且还有“谷歌效应”。

“我们创建像谷歌那样交互式的 Web 应用程序,它们也使用 AJAX”

是啊,但如果你花 95% 的时间在不同浏览器上进行测试,那就没那么酷了。

AJAX 在两个主要方面存在不足:它不支持 ASP.NET WebForms 中的状态管理,也不支持跨浏览器 Web 应用程序。本文的目标就是解决这两个问题。

示例

假设您向用户显示一个树形结构(例如 Windows 资源管理器中的那种),并且只想在用户展开树中的某个文件夹时检索并显示其子文件夹。

使用 AJAX,您将调用一个 Web 服务来检索包含子文件夹名称、链接和图标的数组,然后使用 DHTML 创建树节点。您需要创建 Web 服务方法,编写跨浏览器的 JavaScript 来使其正常工作,并在 ASP.NET WebForm 中使用隐藏字段来保存控件的状态。

OutPost 的作用是通过一个 Web 服务执行一次真实的 postback,该服务还能管理视图状态(并将其保留在 Web 服务器上)。修改过的元素的 `InnerHtml` 会被发送回客户端并在浏览器中显示。这样,整个过程就被泛化了,您无需创建 Web 服务方法、DHTML JavaScript 或为 ASP.NET 控件添加隐藏字段。

因此,使用 OutPost 来实现 Web 应用程序的 AJAX 功能显然能显著提高生产力。

是什么让 OutPost 与众不同?

OutPost 是 AJAX 的一种新思路。其他 AJAX 框架使用您必须自己编写的、专门定制的 Web 服务来返回您所需的数据。而且没有一个框架完全支持 ASP.NET 框架的优势。使用 AJAX,您需要重新思考和重写代码。

OutPost 的独特优势

  • 只需包含一个 .js 文件即可使用 OutPost。
  • OutPost 无需任何代码修改即可将任何 ASP.NET WebForm 转换为 AJAXed WebForm。
  • 它内置了跨浏览器支持。
  • postback 是 100% 真实的,并且会触发您期望的服务器端事件。
  • 支持在同一页面上并排显示多个服务器端 WebForms(.aspx 页面)。
  • 消除了客户端和服务器之间的过多流量(视图状态保留在服务器上,只有修改过的 HTML 被发送回客户端)。
  • 轻量级的 JavaScript 客户端框架使得可以通过脚本动态添加、移除和 postback WebForms。

其他 OutPost 功能

  • 支持 WebForm 级别的输出缓存。
  • 支持 Unicode 字符。
  • 一个 WebForm 拥有自己的 Session。
  • 不需要自定义控件。
  • 主页面和 WebForms 可以在 VS.NET 中设计。

为单个 WebForm 启用 OutPost

是否在页面中添加“AJAX”的决定,不是“我们是否有时间和技能来实现它”的问题。而是变成了一个设计问题。

要添加 OutPost 并为 WebForm 启用 AJAX,您只需在 .aspx 文件开头写入以下内容:

<script src="/OutPost/OutPost.js.aspx" type="text/javascript"></script>

但让我们先暂时退后一步

背景

在开发 Web 上的交互式应用程序时,您有不同的模型可供选择,其中两种模型如下所示。它们可以以多种方式组合,但如果不组合,则对它们的优缺点进行非竞争性比较如下:

ASP.NET WebForms

+ 快速且安全:编译代码,类型安全的代码模型。
+ 开放的 OOP 模型:面向对象编程模型,开放的可扩展类,可重用代码。
+ 框架:拥有一个可扩展的框架,其中包含相当多的快速开发工具和现成的 Web 控件。
+ 状态管理:它使用 viewstate postbacks 为您管理状态。
+ 服务器事件:事件模型负责客户端和服务器之间的通信。
+ 跨浏览器:它生成的 HTML 和 JavaScript 会自动匹配目标浏览器的功能。
+ 缓存的输出:页面、控件和数据集可以以多种方式缓存。
+ 快速启动:只有显示当前页面所需的 HTML 才会被传输到客户端。
- 流量大:postback 时,整个 WebForm(包括 viewstate)都会上传到服务器,然后整个页面会被发送回客户端。
- 重新加载:postback 时,整个页面都会刷新,尤其是在连接速度慢和/或 CPU 慢的情况下,这会导致页面闪烁。

AJAX

+ 流量小:状态不会传输回服务器,只传输所需数据。
+ 无重新加载:页面通过 DHTML 更新(使用 JavaScript 操作 DOM)。
- 无服务器事件:与服务器的通信是通过直接调用不同的 Web 服务方法来完成的。
- 缓慢且不安全:JScript 不是编译型语言,在某些设备上进行测试非常困难。
- 启动缓慢:客户端控件通常需要大量 JavaScript,这些文件会在启动时发送给客户端。
- 非跨浏览器:确保 DHTML 跨浏览器完全取决于开发人员。
- 无框架:您必须自己搭建客户端控件框架来操作 DOM。

OutPost - 两全其美

OutPost 结合了 ASP.NET WebForms 和 AJAX 的优势。OutPost 负责处理以下事项:

  1. 无需跨浏览器 DHTML。
  2. 执行真实的 postback 并触发服务器端事件。
  3. 消除了过多的流量。
  4. 消除了页面重新加载。

这很简单,对吧?是的,让我们一步一步来。

循序渐进

以下步骤描述了使用 OutPost 时 postback 的生命周期:

  1. 无需跨浏览器 DHTML
    • 主页面加载 Web Form 页面,在此过程中(在服务器端),HTML 和 ViewState 会被存储以备后用(在 postback WebForm 时在服务器端再次使用)。此外,所需的 JavaScript 文件链接会被插入到 HTML 的 HEAD 部分(如有必要,还会添加额外的跨浏览器兼容性层脚本)。

      加载 WebForm 时,您可以决定是需要全部 HTML 还是只需要表单的 outerHTML。如果您想在同一主页面上加载多个表单,您只需要每个表单的 outerHTML,并将其添加到您想要的位置(只要它不在页面本身的 WebForm 中)。

      如果您加载单个 WebForm,您可以选择整个页面的 HTML,并将其作为主页面的内容。主页面不一定需要自己的 WebForm。

    • 以上是可选的,您可以自己将 JavaScript 链接插入到代码中,并使用 <outpost:script> 服务器控件来添加所需的跨浏览器兼容性层。

      兼容性层是通过 Atlas(Microsoft ASP.NET 2.0 AJAX 平台)的 AtlasCompat.jsAtlasCompat2.js 文件添加的,它们确保 OutPost 运行时在所有浏览器中都能正常工作。例如,在 windowdocument 元素上,添加了 attachEventdetachEvent 函数,这样 OutPost 运行时中的 DHTML 就可以保持一致。

      如果您不使用主页面,第一次调用的 HTML 结构和 ViewState 将不会存储在服务器上,第一次 postback 将不得不将 ViewState 发送到服务器,并且整个 WebForm 的 inner HTML 也将在第一次 postback 时发送回客户端。

  2. 执行真实的 postback 并触发服务器端事件

    表单的 postback 被替换为对 .NET Web Service 的带外调用。这些调用是从 JavaScript 发出的,而无需重新加载页面。这是通过使用 XmlHttp 向 Web Service 发送 SOAP 信封来完成的。SOAP 信封包含 WebForm 页面的 URI(form 元素的 action 属性)和 WebForm 中输入字段的内容(但不包含 ViewState)。

    然后,Web Service 使用 HttpWebRequestHttpWebResponse 来执行真实的 postback。包含用户代理、语言和会话 cookie 的标头会被发送到 WebForm 页面。

  3. 消除了过多的流量

    HttpWebResponse 读取的 HTML 被解析,具有 ID 的标签/元素会被提取出来并放入一个树状结构中。此步骤仅在 HTML 结构正确时才有效。如果结构不正确,则整个 WebForm 的 inner HTML 将被发送回客户端(但不是 ViewState)。

    将新 postback 的 HTML 树结构与上次 postback 的 HTML 树结构进行比较。这是自底向上的进行的,首先比较表单中不包含任何子节点 inner HTML 的内容。如果没有发现差异,则比较下一个子节点(具有相同 ID 和 ID 索引)从每个树中提取的 inner HTML,但不包含其子节点的 inner HTML。依此类推。

  4. 消除了页面重新加载

    在上一步中找到的差异元素的 inner HTML 被返回到客户端。DOM 会通过跨浏览器的 DHTML 逐个元素地更新。

    仅发送修改过的 innerHTML 片段回客户端似乎是一种有些粗糙的方式,但它对于开发跨浏览器 Web 应用程序来说确实很有意义。

使用代码

单个 WebForm

要添加 OutPost 并为 WebForm 启用 AJAX,您只需在 .aspx 文件开头写入以下内容:

<script src="/OutPost/OutPost.js.aspx" type="text/javascript"></script>

使用主页面加载 WebForm

要从主页面加载一个页面并为其启用 OutPost,您需要在 Page_Load 方法中写入以下内容:

    private void Page_Load(object sender, System.EventArgs e) {
      Page.Controls.Add(OutPost.Core.OutPost.GetLiteralControl(
                               ResolveUrl("Validator7.aspx")));
    }

这(如果放在顶部)会根据客户端用户代理按需添加跨浏览器脚本。

  <%@ Register TagPrefix="outpost" 
               Namespace="OutPost.Controls" Assembly="OutPost" %>
  <outpost:script Runat="server" Path="~/ScriptLibrary/AtlasCompat.js" 
                  Browsers="Netscape,Mozilla,Firefox,Apple,Safari,Mac" />
  <outpost:script Runat="server" Path="~/ScriptLibrary/AtlasCompat2.js" 
                                           Browsers="Apple,Safari,Mac" />

同一页面上有两个相互依赖的表单

使用 JavaScript,可以让两个表单相互依赖。

  <script type="text/javascript">
    //OutPost7.aspx


    function document_onkeyup() {
      if (event.srcElement==Forms[0].TextBox1) {
        Forms[1].TextBox1.value = event.srcElement.value;
      }
    }

    function document_onclick() {
      if (event.srcElement==Forms[0].Button1) {
        Forms[1].Button1.bClicked = true;
        Forms[1].doPostBack('','');
      }
    }
    
    document.attachEvent("onclick",document_onclick);
    document.attachEvent("onkeyup",document_onkeyup);
  </script>

使用客户端框架

以下示例展示了如何动态添加、移除和 postback 页面上的不同表单(请参阅演示项目中的“iGoogle 演示”以及本文开头的演示截图)。

  //RssFeed.js - "The iGoogle Demo"


  function DeleteBox() {
    var oNodeForm = null;
    if (oEventNode) {
      oNodeForm = misc_getForm(oEventNode);
      oEventNode = null;
    }
    if (oNodeForm!=null) {
      oNodeForm.parentNode.parentNode.removeChild(oNodeForm.parentNode);
      Forms[oNodeForm.iIndex] = null;
    }
    var sAction = unescape(oNodeForm.action);
    FormMain.bAddBox = false;
    FormMain.doPostBack("oButtonDelete",sAction);
  }
  
  function AddBox() {
    var sUri = "/OutPost/Demo/RssFeed.aspx?rss="+ 
        escape(document.getElementById('FormMain').oTextBoxAddRssUri.value);
    
    for (var i in Forms) {
      if ((Forms[i]!=null) && (unescape(Forms[i].action)==unescape(sUri))) {
        alert("The feed has already been added");
        return;
      }
    }
  
    var oNodePanel = document.createElement("DIV");
    oNodePanel.className = "cPage";
    
    var oNodeColumn = document.getElementById("oTableCell2");
    if (document.getElementById("oTableCell1").childNodes.length
                            <oNodeColumn.childNodes.length) {
      oNodeColumn = document.getElementById("oTableCell1");
    }
    if (document.getElementById("oTableCell3").childNodes.length
                            <oNodeColumn.childNodes.length) {
      oNodeColumn = document.getElementById("oTableCell3");
    }
    
    oNodeColumn.appendChild(oNodePanel);
    
    var oNodeForm = createForm("Form1", sUri, AddBox_onerror);
    oNodePanel.appendChild(oNodeForm);
    document.getElementById('FormMain').oTextBoxAddRssUri.value = "";
  }
  
  function AddBox_onerror() {
    alert("Error adding RSS feed");
  }
  
  function FormMain_onsuccess() {
    this.base_onsuccess();
    if (this.bAddBox!=false) {
      AddBox();
    }
    this.bAddBox = true;
  }
  
  function window_onload() {
    FormMain.onsuccess = FormMain_onsuccess;
  }
  
  window.attachEvent("onload", window_onload);

OutPost 的未来

  • 实现发送更小规模更改的可能性(例如,只设置输入框的值或只更改下拉列表中的某个选项)。
  • 我想整合简单的 AJAX 行为,以便您可以与 Web 服务器上的业务逻辑进行交互。
  • 解决书签问题。无法为 WebForm 页面添加书签,我正在寻找解决方案。
  • 解决后退按钮问题。无法使用后退按钮,我正在为此寻找解决方案。

历史

  • 2005 年 9 月 - OutPost 1.0 版 - 添加了跨浏览器支持、轻量级客户端框架。
  • 2005 年 5 月 - OutPost 0.5 版 - 支持单个 WebForm,仅限 IE。
  • 2004 年 11 月 - 进行了首次初步测试。

更新

  • 4/10-2006 - 1.5.003
    • 添加了对 EventValidation 和 ViewState 输入(包括周围的 DIV)的更彻底移除。
    • 移除了未使用的 JavaScript 部分。
  • 1/10-2006 - 1.5.002
    • 比较 HTML 时移除了 selected="selected",因为它导致下拉框在没有必要的情况下被刷新。
    • 由于对下拉框的更改,移除了每次 postback 后对 oForm_doReset 的调用。
    • 更新了 SignUp 演示 - 它现在有三个“链接”控件(两个下拉列表和一个文本字段)。
  • 1/10-2006 - 1.5.001
    • 内部添加了 gzip 和 deflate 压缩。
    • 将命名空间从 OutPost.Core 更改为 OutPost
    • 移除了常量 ContentTypeForNoCompression
  • 28/9-2006 - 1.4.004
    • 尝试直接查看 OutPost.aspx 时添加了错误消息。
    • 添加了常量 MinimumSizeForCompressionContentTypeForNoCompression
  • 28/9-2006 - 1.4.003
    • 将 "SessionID" 和 "session" 更改为 "GUID" 和 "guid"。
    • 通过移除空格和替换变量名来优化客户端框架大小。
  • 27/9-2006 - 1.4.002
    • 为 GET 请求添加了 PartsNeeded 的默认设置。
    • 将 "SessionID" 和 "session" 更改为 "GUID" 和 "guid"。
  • 26/9-2006 - 1.4.001
    • OutPost.asmx 替换为 OutPost.aspx,将 OutPost.js.aspx 替换为 OutPost.aspx/Js
    • 优化了客户端框架大小。
  • 25/9-2006 - 1.3.002
    • 优化了 POST 请求大小。
    • 优化了请求和响应的正文大小。
    • 放弃了 SOAP 信封。
  • 21/9-2006 - 1.3.001
    • 发现了 Unicode 字符的问题。目前,您的页面必须包含 ResponseEncoding="utf-8" 标签才能正确处理 Unicode 字符。
    • 在 IE 中 postback 时,在表单上添加了一个披风(事件陷阱),以模仿默认的 WebForm 行为。
    • 添加了对 WebForm_AutoFocus 的支持。
    • 添加了对请求排队的ваты。
    • 添加了对重新加载脚本块的支持。
    • 添加了 LocalHost 设置,用于指定备用 localhost 域名。
    • 添加了 HTTP 压缩支持(已通过 FlatCompression R1.20.226 测试)。
  • 17/3-2006 - 1.2.002
    • 移除了 Session 依赖。
    • 在 IE 中 postback 时恢复位置和选择。
  • 16/11-2005 - 1.2.001
    • 在第一次 post back 时添加了 Session 支持。
    • 添加了过滤器,以利用第一次 post back 的差异。
  • 16/11-2005 - 1.1.002
    • 添加了更多 CookieContainers。
    • 合并了 ASP1.1 和 ASP2.0 的核心文件。
  • 18/10-2005 - 1.1.001
    • OutPost.asmxOutPost.js.aspx 的路径从 web.config 读取。
    • 支持 Netscape。
    • 支持 Opera。
  • 14/10-2005 - 1.1.000
    • 支持 HTTPS/SSL。
    • 支持 Visual Studio 2005 Release Candidate。
  • 7/10-2005 - 1.0.002
    • 修复了 JavaScript Serializer 中的一个 bug:Unicode 字符未被正确转义。
    • 使用 StringBuilder 优化了 JavaScript 属性。
    • 默认值在 PostBack 之前存储,并在 PostBack 之后恢复。
  • 4/10-2005 - 1.0.001
    • 修复了构造函数中的一个 bug:仅当设置了 "PartsNeeded" 标头时,PartsNeeded 才会被初始化为 StringArray
    • 修复了 window_onload 函数中的一个 bug:如果页面上没有表单,会导致脚本错误。
© . All rights reserved.