OutPost:通过 XmlHttp 进行 PostBack






4.49/5 (17投票s)
无需重新加载整个页面即可实现 ASP.NET WebForms 的真实 postback。
演示项目中“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 负责处理以下事项:
- 无需跨浏览器 DHTML。
- 执行真实的 postback 并触发服务器端事件。
- 消除了过多的流量。
- 消除了页面重新加载。
这很简单,对吧?是的,让我们一步一步来。
循序渐进
以下步骤描述了使用 OutPost 时 postback 的生命周期:
- 无需跨浏览器 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.js 和 AtlasCompat2.js 文件添加的,它们确保 OutPost 运行时在所有浏览器中都能正常工作。例如,在
window
和document
元素上,添加了attachEvent
和detachEvent
函数,这样 OutPost 运行时中的 DHTML 就可以保持一致。如果您不使用主页面,第一次调用的 HTML 结构和 ViewState 将不会存储在服务器上,第一次 postback 将不得不将 ViewState 发送到服务器,并且整个 WebForm 的 inner HTML 也将在第一次 postback 时发送回客户端。
- 主页面加载 Web Form 页面,在此过程中(在服务器端),HTML 和 ViewState 会被存储以备后用(在 postback WebForm 时在服务器端再次使用)。此外,所需的 JavaScript 文件链接会被插入到 HTML 的
- 执行真实的 postback 并触发服务器端事件
表单的 postback 被替换为对 .NET Web Service 的带外调用。这些调用是从 JavaScript 发出的,而无需重新加载页面。这是通过使用 XmlHttp 向 Web Service 发送 SOAP 信封来完成的。SOAP 信封包含 WebForm 页面的 URI(
form
元素的action
属性)和 WebForm 中输入字段的内容(但不包含 ViewState)。然后,Web Service 使用
HttpWebRequest
和HttpWebResponse
来执行真实的 postback。包含用户代理、语言和会话 cookie 的标头会被发送到 WebForm 页面。 - 消除了过多的流量
从
HttpWebResponse
读取的 HTML 被解析,具有 ID 的标签/元素会被提取出来并放入一个树状结构中。此步骤仅在 HTML 结构正确时才有效。如果结构不正确,则整个 WebForm 的 inner HTML 将被发送回客户端(但不是 ViewState)。将新 postback 的 HTML 树结构与上次 postback 的 HTML 树结构进行比较。这是自底向上的进行的,首先比较表单中不包含任何子节点 inner HTML 的内容。如果没有发现差异,则比较下一个子节点(具有相同 ID 和 ID 索引)从每个树中提取的 inner HTML,但不包含其子节点的 inner HTML。依此类推。
- 消除了页面重新加载
在上一步中找到的差异元素的 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 部分。
- 添加了对 EventValidation 和 ViewState 输入(包括周围的
- 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 时添加了错误消息。
- 添加了常量
MinimumSizeForCompression
和ContentTypeForNoCompression
。
- 28/9-2006 - 1.4.003
- 将 "SessionID" 和 "session" 更改为 "GUID" 和 "guid"。
- 通过移除空格和替换变量名来优化客户端框架大小。
- 27/9-2006 - 1.4.002
- 为 GET 请求添加了
PartsNeeded
的默认设置。 - 将 "SessionID" 和 "session" 更改为 "GUID" 和 "guid"。
- 为 GET 请求添加了
- 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.asmx 和 OutPost.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:如果页面上没有表单,会导致脚本错误。
- 修复了构造函数中的一个 bug:仅当设置了 "