ASPNET 客户端回调代表着一种简洁而绝佳的方法它可以在不发布和刷新当前页的情况下执行服务器端代码我在 年 月和 月的 Cutting Edge 专栏中讨论了 ASPNET 回调当时是从对服务器进行后台回调向相关页发送输入数据以及接收响应的呈现页的角度对它们进行了讨论然后响应字符串由合适的客户端进行处理并且通常通过动态 HTML (DHTML) 对象模型和嵌入到页面中的回调 JavaScript 函数来操作呈现的页面内容
尽管回调的这种用法已经让人非常激动了但它们还可以执行更多的任务脚本回调机制也可以为服务器控件添加高级功能通过实现几个接口任何自定义控件都会被赋予脚本回调功能以便使用后台往返来收集服务器数据以及更新用户界面 — 这就是本月我要讲述的主题
受 GridView 控件的启发
如果您读过我最近写的一篇功能文章 ASPNET GridView您就会了解 GridView 控件无需刷新整个页面就可以显示新的记录页实际上GridView 控件提供了一个基于 ASPNET 脚本回调进行分页和排序的高级引擎新页面的数据是在后台下载的用户看不到在数据到达客户端之后这些数据将立即由 JavaScript 函数收集并用于更新当前视图
分页和排序回调并不是 % 的客户端回调解决方案(如果您需要一个纯粹的客户端实现请参阅 年 月 Jeff Prosise 在 Wicked Code 专栏中发表的文章)GridView 的分页和排序回调是按需工作的它只下载需要的数据而不会将整个数据源都下载到客户端上您仍然要付出一个往返的代价但是能够保证得到最新的数据即使这些数据最近已经在服务器上更新过
自从发现 ASPNET 控件可以支持脚本回调功能之后我感到非常兴奋同时也促使我赶紧找出构建自己的脚本回调的方法
顺便提一句GridView 并不是唯一一个支持类似功能的 ASPNET 控件其他视图控件(如 TreeViewDetailsView 和 FormView)也能以其他方式提供相同的功能作为使用具有回调功能控件的开发人员您不需要处理服务器端代码也不用担心编写以及在宿主页中嵌入 JavaScript 代码的问题该控件可以完成一切操作它展示了一个直观的编程模型您可以通过该模型控制脚本回调机制
控件脚本回调基本知识
ASPNET 脚本回调机制由两个关键元素组成响应用户操作的服务器端代码以及客户端上处理服务器端事件所生成结果的 JavaScript 回调代码在页面回调自身的情况下正如我在前面提到的文章中所述的那样您可以在执行对用户不可见的回发的页面按钮中附加一些 ASPNET 生成的脚本代码因为该请求的目标是当前页所以该页会发布到自身这与它在一个普通回发事件中的行为方式相似只是页面的生命周期缩短了该页必须实现 ICallbackEventHandler 接口以便可以调用一个具有预定义签名的方法来为客户端生成结果
那么当控件触发带外调用时该方案又有什么不同呢?在这种情况下不可见回发的目标 URL 是承载该调用方控件的页面的 URL该控件必须实现 ICallbackEventHandler 才能提供为客户端生成某些结果的方法同样该控件负责在承载页中插入处理结果和刷新该页所需的任何 JavaScript 代码
具有回调功能的控件只是一个实现 ICallbackContainer 和 ICallbackEventHandler 接口的控件两个接口都各有一个方法ICallbackContainer 接口具有的方法可以返回触发远程调用的脚本代码ICallbackEventHandler 接口则提供了在调用期间执行的服务器端代码ICallbackEventHandler 也是一个具有回调功能的页面必须实现的接口一个实现回调接口的自定义控件示例的声明如下面的代码所示
public class CallbackValidator : WebControl
INamingContainer ICallbackContainer ICallbackEventHandler
在 ICallbackContainer 接口的实现中您可能需要放入一个对该页 GetCallbackEventReference 方法的调用以获得一个可启动服务器事件的正确 JavaScript 调用稍后我再讲述这些内容
CallbackValidator 控件
为了解具有回调功能的服务器控件我们来看一个具有 ASPNET 脚本回调功能的自定义验证器控件示例在 ASPNET 中验证控件用于检查并验证网页中定义的窗体域的输入验证器是一个服务器控件它是从 BaseValidator 类继承的而该类又是从 Label 继承的
每个验证控件都引用一个位于该页其他位置的输入控件当页面要提交时任何受监视服务器控件的内容都会传递到该验证器以进行进一步处理每个验证器都执行一种不同类型的验证例如CompareValidator 控件使用比较运算符(如小于等于或大于)将用户的输入与一个固定值进行比较RangeValidator 确保用户输入位于某个指定范围内而 RegularExpressionValidator 只在匹配某个常规表达式定义的模式时才验证用户输入
通常验证都在服务器上发生然而 ASPNET 还为大多数验证控件提供了一个完整的客户端实现并允许用户为其余验证控件编写自定义客户端脚本这就使得具有 DHTML 功能的浏览器(如 Microsoft?Internet Explorer 和更高版本)在用户点击或单击受监视输入域之外的位置后能够立即在客户端上执行验证在很多情况下客户端验证足够强大可以检测出许多重大错误并通知用户例如RequiredFieldValidator 控件可验证给定域不能保留为空无需回发到服务器即可验证当前值
如果客户端验证打开则在所有输入域均包含有效数据之前该页不会回发为了运行安全代码以及防止恶意和秘密的攻击您还是应该在服务器上验证数据服务器端验证始终由验证器控件执行即使同时要执行客户端验证也是如此另外并非所有类型的验证都能在客户端上完成实际上如果您需要针对数据库进行验证则没有别的选择只能回发到服务器而这也正是发生问题的地方
常规回发涉及整个页面上载整个视图状态处理整个页面生成下载和呈现同样的大型响应如果您能够向服务器发出经过优化的带外请求并只检查验证之下的控件的状态那岂不是很好?
在 ASPNET 中没有这样的控件那么我们就来编写一个这样的控件吧我将其命名为 CallbackValidatorCallbackValidator 是一个自定义 ASPNET 控件我构建这个控件的目的是为了演示控件可以如何实现对承载页的带外调用以及如何在服务器上自行处理事件
在我开始着手此项目时实际上并没有如此雄心勃勃的目标我原先的目标只是修改 CustomValidator 标准控件对于该记录CustomValidator 控件采用了以编程方式定义的验证逻辑来检查用户输入的有效性如果预先不知道要检查的值则应该使用此方法CallbackValidator 控件的最初意图是提供一种方法以便在不回发整个页面的情况下执行服务器端验证我意识到无需太多的额外努力就可以拥有一个类似于自定义按钮的控件这个控件可以在不回发整个页面的情况下在服务器上对许多输入域进行验证而此时我的修改工作已经完成了一半这个行为就是 CallbackValidator 控件的全部
在我深入讲述该控件的精髓之前我们先来看一下图 该页面上的 Submit 按钮只会按照普通的方式将所有值发布到服务器上实际上这些值将在客户端上进行处理如果所有这些值都需要传递那么该控件就会将其传递到服务器上在该服务器上所有控件输入都将使用服务器端验证代码(如果有的话)进行验证Validate 按钮会触发一个对 Web 服务器的带外调用并只验证指定的输入控件在它返回时您就会知道哪些值已经通过了服务器的验证例如在图 中您将在尝试提交其余数据之前了解到是否已经采用了该用户 ID
图 带有具有回调功能验证的输入窗体图 显示了该页面的源代码正如您可以看到的那样它包含了一个 HTML 服务器窗体一些文本框(每个文本框都绑定到一个标准的验证控件)以及该自定义 CallbackValidator 控件的一个实例此控件实际上负责创建并显示 Validate 按钮
该控件如何工作
该 CallbackValidator 控件从 WebControl 继承并实现了 INamingContainer 接口另外它还实现了 ICallbackContainer 和 ICallbackEventHandler 接口以便获得回调支持
ICallbackContainer 接口需要方法 GetCallbackScript 按照下列方式声明
string GetCallbackScript(IButtonControl buttonControl string argument)
GetCallbackScript 采用两个参数第一个是对预期要触发回调的页面控件的引用第二个参数(字符串)表示调用方希望传递给方法以帮助构建输出的任何上下文从名称可以看出GetCallbackScript 方法使用 JavaScript 函数调用来准备和返回字符串以便附加到指定的按钮控件来触发远程调用
该按钮控件参数使您能够精确地指定要对控件 UI 中的哪个按钮进行 JavaScript 调用该示例 CallbackValidator 控件只有一个可单击按钮而 GridView 控件则具有很多可单击按钮每个按钮都用于页导航或标头中的一个链接按钮在 ASPNET 中所有充当窗体中按钮角色的控件都需要实现一个新的接口 — IButtonControl该接口在