官方的说明中提到ValidatorCalloutExtender并不支持CustomValidator的服务器端验证,而我又迫切需要这个功能,于是使用google大法,找到了这篇名为 《Validating Server Side using the Validation Callout Extender with the Custom Validator》 的文章,它提到了可以通过让Page类实现System.Web.UI.ICallbackEventHandler接口从而使页面本身支持异步回调,接着指定CustomValidator的ClientValidationFunction属性,把验证事件关联到一个客户端的js函数,在该函数中通过异步回调获得服务器端验证结果后返回,那么对于ValidatorCalloutExtender而言,没有与服务器端通信,只是从js中获得了一个验证结果,效果自然是大大的好。

不过,这个方法太烦杂了,上次看了老赵的这篇 分清ASP.NET AJAX中的Extender和Behavior模型 当时就受到了启发,在服务器端验证事件中,若验证失败,则注册一个启动脚本,内容是调用ValidatorCalloutExtender的Behavior中显示气泡提示的方法,那么当这个启动脚本被执行不就大功告成了嘛!

说干就干,首先摸索了一下那个弹出气泡的方法名,居然就是show(),要是不对恐怕还要到官方文档里面查,接着在页面中添加一个RegularExpressionValidator,因为如果ValidatorCalloutExtender直接关联带有服务器端验证的CustomValidator会报错,所以需要一个替代品,例如:
< asp:RegularExpressionValidator  ID ="rev"  runat ="server"  ErrorMessage ="用户名密码错误!"  
                    ControlToValidate
="Password"  Display ="None"  ValidationExpression ="\w*|\W*" ></ asp:RegularExpressionValidator >   
其中,ErrorMessage即弹出气泡中的提示文字,若提示内容会由于服务器端验证结果动态变化,则可以在后台代码中动态改变该属性从而达到目的;ControlToValidate为弹出气泡指向的文本框;Display设为none用于隐藏这个替代品;正则表达式设为“\w*|\W*”表示任意字符都能通过验证,这样就不会出现客户端正则表达式验证没通过而激活气泡提示的情况了。接着添加一个ValidatorCalloutExtender,注意要指明BehaviorID属性
                 < cc1:ValidatorCalloutExtender  ID ="vceLogin"  runat ="server"  TargetControlID ="rev"  BehaviorID ="vceLoginBehavior" >
                
</ cc1:ValidatorCalloutExtender >
接下来转到后台代码,在验证失败后添加一条注册启动脚本的语句
ScriptManager.RegisterStartupScript(UpdatePanel1,  this .GetType(),  " loginFailed " " window.setTimeout(\ " $find( ' vceLoginBehavior ' ).show();\ " ,500); " true );                
之所以在js中用到了延时,是因为之前不用延时直接执行会发生$find('vceLoginBehavior')对象为Null的意外,遗憾的是原因我到目前也没有找到,麻烦哪位高人指点一下哈,在下先谢过!这个写法自然不够安全,最好能写成若$find('vceLoginBehavior')对象为空,隔一段时间再执行直到执行成功为止,这儿就先偷个懒,毕竟我的这个程序大多是内网用的,网速可以保证。如果觉得有必要在气泡弹出时将焦点移动到气泡所指向的文本框,那就再追加一句
ScriptManager1.SetFocus(txtPassword);
OK,大功告成!实际测试时还发现了ValidatorCalloutExtender的一个小bug,气泡部分可能被页面上的控件遮住,没设z-index的关系,不得以改了源码,AjaxControlToolkit\ValidatorCallout\ValidatorCalloutBehavior.js的第68行追加一句
popupTable.style.zIndex  =   " 1000 " ;
在漫长的编译过程中,想了下为什么这个控件没有指定css的class,也没有指定id,本来只要有其中任意一个,我就不用来改源码了...
以上内容适用于10618,更新到10920后,按上述做法居然出现问题,请听我继续分解...
版本更新后,运行时报错“this._popupBehavior为空或不是对象”,郁闷不已。只能把两个版本的ValidatorCalloutBehavior.js文件拿过来对比,发现问题出在直接调用behavior的时候,不会进行DOM构造了,因此找到了被封装的用于构造DOM的函数_ensureCallout()执行之,其后调用show()方法,顺利解决问题!
也就是原先的代码改为
ScriptManager.RegisterStartupScript(UpdatePanel1,  this .GetType(),  " loginFailed " " window.setTimeout(\ " $find( ' vceLoginBehavior ' )._ensureCallout();$find( ' vceLoginBehavior ' ).show();\ " ,500); " true );

另外,新版本果然多了个id,于是添加z-index不用更改源代码了,只需要在css文件中加上一句
        #vceLoginBehavior_popupTable,#vcePwd_popupTable,#vceUsername_popupTable
        
{
            z-index
:1000;
        
}
   
上面的例句适用于多个ValidatorCalloutExtender存在的情况,只要写一次就可以了,几个id之间用逗号隔开。behavior的命名规则是这样的:如果已经被指定,则使用指定名称拼上"_popupTable"否则会使用Extender的id拼上"_popupTable"。舒服~