Magic AJAX:将 AJAX 应用于现有网页






4.82/5 (66投票s)
如何在不替换 ASP.NET 控件和/或编写 JavaScript 代码的情况下,将 AJAX 技术应用到您的网页。
引言
在 AJAX 技术如此盛行和备受关注之后,现在开发者有许多 AJAX 解决方案可供选择。缺点是您必须用自定义的 AJAX 控件替换网页中的标准 ASP.NET 控件,和/或为客户端编写 JavaScript 代码来处理服务器返回的数据。
如果有一个“神奇”的面板控件,当您拖放普通的、老式的 ASP.NET 控件时,“神奇地”将它们转换为支持 AJAX 的控件怎么办?不可能!嗯,这篇文章声称这样的面板确实存在(AjaxPanel
),而且遗憾的是,它没有任何魔法;全都是纯粹的 C# 代码。
更新 - 2005 年 11 月 12 日: MagicAjax 框架现已托管在 SourceForge。自最初发布以来,已经增加了许多改进和功能,包括对 ASP.NET 2.0 的支持。
背景
本文假设您知道什么是 AJAX。如果不是,CodeProject 上有许多好文章可以帮助您入门。本文的代码基于 Bill Pierce 出色的系列文章 AJAX WAS HERE。要使用本文中的类,您不需要理解客户端 JavaScript 框架及其如何调用服务器方法,但如果您想了解“幕后”发生了什么,我强烈建议阅读 Bill Pierce 的文章。
使用代码
我包含了一个演示项目,向您展示如何将现有的普通回发控件转换为类似 AJAX 的控件。
BubisChat.aspx
这个页面试图实现一个聊天应用程序。我不会深入探讨它是如何工作的;它仅用于演示目的。它使用标准的 ASP.NET 控件,没什么神秘的。按钮会导致向服务器回发,页面会重新加载并用新数据填充控件。
AjaxBubisChat.aspx
此页面使用相同的控件,但工作方式却大不相同。控件即时刷新,无需浏览器重新加载。没错,AJAX 来了。
嘿,你是怎么做到的
将 *BubisChat.aspx* 转换为 *AjaxBubisChat.aspx*(或将 AJAX 应用到任何使用此框架的页面)需要四个步骤。
-
让页面继承自 AjaxPage(可选)
public class AjaxBubisChat : Ajax.AjaxPage
继承自
AjaxPage
不是必需的;它仅处理回调事件并提供一些有用的属性以方便使用。要在继承自AjaxPage
的页面中处理回调事件,请重写OnCallBack
方法。protected override void OnCallBack(EventArgs e) { // Refreshes the sessionID in the cache PutSessionIDInCache(); txtMsg.Text = chatData.msgText.ToString(); ShowNames(); base.OnCallBack (e); }
如果页面不继承自
AjaxPage
,则可以通过实现ICallBackEventHandler
接口来处理回调事件。public interface ICallBackEventHandler { void RaiseCallBackEvent(); }
回调类似于回发,但无需浏览器重新加载页面。我稍后会解释回调的作用。出于稍后会变得清晰的原因,在回调期间,页面及其控件的 Load 事件不会被引发。您必须改用回调事件。
在回调期间,页面的
HttpContext
是无效的,因此您无法使用System.Web.UI.Page
的Request
/Response
属性,而必须使用CallBackHelper.Request
和CallBackHelper.Response
属性。AjaxPage
提供了有效的Request
/Response
属性,因此您无需在代码中将它们替换为CallBackHelper
中的属性。 -
将需要无回发刷新的控件放入 AjaxPanel 中。
每个
TextBox
和ListBox
可以有一个AjaxPanel
,或者所有这些控件可以有一个AjaxPanel
。按钮应放在AjaxPanel
中,以便它们的提交功能自动替换为回调函数。在此示例中,为了方便起见,我只将所有控件放在一个AjaxPanel
中。 -
配置 web.config 文件
将
<httpModules> <add name="AjaxHttpModule" type="Ajax.AjaxHttpModule, Ajax" /> </httpModules>
放在
<system.web>
部分,并将<!-- If CallBackScriptPath is not set in the appSettings, "/ajax/script" is used--> <add key="CallBackScriptPath" value="/ajax/script" />
放在 web.config 文件的
<appSettings>
部分。AjaxHttpModule
在HttpApplication
的AcquireRequestState
事件中处理回调,该事件在请求经过身份验证之后。如果您将源文件提取到 wwwroot 路径,则默认脚本路径是有效的。如果您将其提取到其他目录,则应相应地更改应用程序设置中的CallBackScriptPath
。 -
启用页面上的 CallBackTimer(可选)
// For automatic CallBack every 3 seconds. CallBackHelper.SetCallBackTimerInterval(3000);
如果聊天文本框需要自动刷新以显示新消息,则需要此设置。大多数页面不需要自动刷新,因此如果不是这种情况,请忽略此项。
信不信由你,就这么简单!无需 JavaScript,也无需替换控件。您可以通过代码或使用 Visual Studio Designer 将新控件添加到 AjaxPanel
。
那么,回调做了什么?
回调的作用是调用服务器端控件事件(以及一个特殊的 CallBackTimer
事件,如果已启用)。有关更多详细信息,我建议阅读我在背景部分提到的 Bill Pierce 的文章。当服务器收到回调时,它会返回通用的 JavaScript 代码。客户端并不关心 JavaScript 做什么(填充 ListBox
、调用警报框,等等),它只是执行它们。因此,与通常的 AJAX 应用程序的心态相反,由服务器负责使用 JavaScript 操作页面,而不是客户端。这样,与其试图将 JavaScript 代码嵌入网页并将其与服务器代码同步,不如将重点放在服务器端实现自定义控件的所有功能,而无需将代码分离到 JavaScript 部分和 C#(VB.NET,等等)部分。
现在,事情变得更有趣了……支持 AJAX 的页面被存储为会话状态变量。当调用回调时,AjaxHttpModule
会拦截它,从会话中找到原始页面,并调用页面控件的相应事件,而无需重新加载原始页面。
为什么将页面存储在会话中?
- 由于页面及其控件不断重新加载,因此没有开销。
- AJAX 的错觉是页面表现得像桌面应用程序。在桌面应用程序中,控件会一直保留在内存中,而无需每次按下按钮时都重新加载它们,并且可以动态地添加或删除控件。嗯,通过将页面持久化到会话中,我们不必重新加载控件,并且可以动态地添加或删除控件;如果您将它们添加到
AjaxPanel
,它们实际上也会出现在浏览器中!
为了使页面能够存储在会话中,它必须至少包含一个 AjaxControl
控件(AjaxPanel
的基类)。使用的会话密钥是原始页面的 URL,以便区分不同的页面。
回调不一定来自 AjaxPanel
包含的控件,它可以从页面上的任何控件调用,只要该控件已正确配置为调用相应的回调函数,如下所示:
Button btnSend = new Button();
btnSend.Attributes.Add ("onclick",
CallBackHelper.GetCallbackEventReference(btnSend) +
" return false;");
CallBackHelper.GetCallbackEventReference
方法在客户端提供 AJAXCbo.DoPostCallBack
调用,并添加了 `return false;` 以便 OnClick
事件可以覆盖提交函数。
默认情况下,AjaxPanel
会自动配置其包含的所有提交按钮以调用回调函数,并将普通回发的 __doPostBack
调用替换为 AJAXCbo.DoPostCallBack
调用。如果您想手动设置控件的 OnClick
事件,请将 AjaxPanel
的 SetCallBackForSubmitButtons
和 SetCallBackForChildPostBack
属性设置为 false
。
神秘的 AjaxPanel
AjaxPanel
的任务是在每次调用回调时将内容反映到客户端浏览器。为了实现这一点,它会扫描其包含的控件,并为每个添加、删除或修改的控件生成相应的 JavaScript 代码,而忽略未发生更改的控件。为了检测更改,它会渲染每个控件并检查生成的 HTML 是否与上一次回调期间获得的 HTML 不同。
此外,如果 AjaxPanel
遇到任何 RenderedByScriptControl
控件(AjaxPanel
继承自此类),它会忽略它们,并让它们负责将自己“反映”到浏览器。因此,如果一个 AjaxPanel
(父级)包含另一个 AjaxPanel
(子级),并且子 AjaxPanel
的控件发生更改,父 AjaxPanel
不会发送子 AjaxPanel
的整个 HTML 渲染,而是子 AjaxPanel
只发送更改控件的 HTML。这样,客户端响应回调的 JavaScript 代码的大小就会大大减小。
限制
已知限制包括:
- 您无法删除
AjaxPanel
控件的属性。删除不会反映在页面上。请使用ajaxPanel.Attributes["attrib"] = "";
实例。
- 默认情况下,
AjaxPanel
中的控件不会调用客户端页面验证。您需要“手动”在控件的OnClick
事件中插入适当的 JavaScript。 - 仅在 Internet Explorer 和 FireFox 中进行测试。如果浏览器不支持
XmlHttp
,则没有替代方案。 - 会话必须是“
InProc
”模式。“SQLServer
”和“StateServer
”模式不受支持。
总结
我将不深入研究类的内部工作细节。我已尝试记录所有方法,因此任何想扩展提供的 AJAX 控件的人,我强烈建议阅读所有方法和类的注释。对于那些只想使用框架的人
- 演示页面的脚本路径仅在您将源文件提取到 wwwroot 路径时才有效。如果您将其提取到其他目录,则应相应地更改 web.config 的应用程序设置中的
CallBackScriptPath
。 - 请遵循我在本文使用代码部分提到的四个步骤。
- 阅读 AjaxPage.cs、CallBackHelper.cs 和 ICallBackEventHandler.cs 的注释。
- 阅读
AjaxPanel
的public
属性的注释。 - 阅读
AjaxLinkButton
控件的注释。 - 您可以使用
CallBackHelper.Write
将自定义 JavaScript 发送到客户端。 - 在您的页面代码中,您必须使用
CallBackHelper.Request
和CallBackHelper.Response
属性,除非它继承自AjaxPage
。同样适用于您的用户控件和提供的AjaxUserControl
。 - 不要在
AjaxPanel
中使用Panel
控件,因为即使Panel
的一个子项发生更改,所有子项都必须在客户端渲染。请改用AjaxPanel
控件。 - 在 2005 年 9 月 23 日的更新后,该框架可以在回调期间处理
Response.Redirect
和Server.Transfer
方法。您无需在代码中更改它们。 - 在回调期间,请使用
CallBackHelper.End
而不是Response.End
。 - 始终牢记,回调与回发不同,因为页面及其控件会持久化在会话中,因此不会引发
Load
事件,并且您可以动态地向AjaxPanel
添加/删除控件,更改将反映在浏览器中。有点像操作桌面应用程序的控件。
关注点
AjaxPanel
还可以处理“浏览器后退按钮”问题。“浏览器后退按钮”问题是指,按下后退按钮时,浏览器会从缓存中加载 HTML 页面,因此对页面所做的任何 AJAX 更改都会丢失,而用户仍然期望看到他们之前查看过的页面。
为了解决这个问题,我在页面中放置了一个每次页面加载时都会执行的 JavaScript 函数和一个空的隐藏字段。该函数会检查此隐藏字段,如果为空,它会假设页面是通过请求服务器加载的(即通过刷新按钮),然后设置字段的值。如果函数发现字段不为空,它会假设浏览器是通过后退按钮加载页面的(字段的值会被恢复),并调用服务器上的一个特殊回调(CallBackStartup
)。当引发 CallBackStartup
事件时,AjaxPanel
会将所有子项渲染到客户端页面,从而恢复用户之前查看的页面。
结论
我希望您觉得这个框架很有用。我鼓励您尝试使用它,如果您从中创造出一些奇特、令人惊叹、令人眼花缭乱、令人瞠目结舌的控件,请与我们分享。
历史
- 2005 年 9 月 16 日 - 初始发布。
- 2005 年 9 月 16 日 - 在文章中添加了浏览器检测限制,由 Cristian O. 指出。
- 18-9-2005
- 现在无需继承自
AjaxPage
即可使用 AJAX Framework。将其功能移至其他类,除了回调事件的处理和Request
/Response
属性。 - 添加了 AjaxUserControl.cs,其功能与
AjaxPage
类似。 - 用
AjaxHttpModule
替换了AjaxHttpHandler
。 - 添加了 IPostDataLoadedEventHandler.cs。
- 添加了 NoVerifyRenderingPage.cs。
- 在 CallBackHelper.cs 中添加了一些辅助函数和属性。
- 其他小的添加和改进。
- 更新了文章的文本。
- 修复了
AjaxPanel
的SetCallBackForChildPostBack
中的一个错误,由 Cristian O. 指出。
- 现在无需继承自
- 21-9-2005
- 修复了 CallBackObject.js 以使复选框正常工作(由 collab man 指出)。
- 添加了
CallBackHelper.Redirect
方法,并将其包含在文章的总结部分(由 mdissel 指出)。
- 23-9-2005
AjaxHttpModule
现在可以在回调期间处理Response.Redirect
和Server.Transfer
。不需要CallBackHelper.Redirect
。更新了文章的总结部分。
- 27-9-2005
- 修复了一个由 collab man 指出的错误(当由
CheckBox
或ListBox
调用回调时,控件未刷新)。 - 修复了一个由 JunkyMail1 指出的错误(多选
ListBox
工作不正常)。 - Cristian O. 提供了对 CallBackObject.js 的增强(“加载中...”指示器、回调超时、错误期间返回的响应显示在页面上)。
- 修复了一个由 collab man 指出的错误(当由
- 30-9-2005
AjaxPanel
现在在其Visible
属性更改时能正常工作。- 修复了由 mdissel 指出的隐藏控件持久化状态的错误。
- 6-10-2005
- 修复了由 CarinLindberg 指出的
RadioButtonList
问题。
- 修复了由 CarinLindberg 指出的
- 8-10-2005
- 在 CallBackObject.js 中,修复了当 POST 数据包含 '+' 时无法正确发送的问题,由 Ricardo Stuven 指出。
- 11-10-2005
- 包含了 Ricardo Stuven 提供的处理
CheckBoxList
的修复。
- 包含了 Ricardo Stuven 提供的处理
- 12-11-2005
- 在文章中包含了 MagicAjax 的 SourceForge 和主页链接。