/*
本章讨论ASP.NET的验证控件框架,并对验证控件体系进行扩展。
*/
从Winforms转到ASP.NET的开发者通常会惊讶于ASP.NET提供的用户输入验证架构会如此方便。而且这个验证框架天生就支持客户端验证和服务端验证双重验证,如图7-1所示,对客户输入验证的实现也起到了规范的作用。

图7-1 ASP.NET双重验证结构
7.1 ASP.NET验证框架概述
从ASP.NET 1.x开始,验证框架就实现了客户端和服务端双重验证的逻辑,ASP.NET 2.0要更加强大一些,比如它实现了验证组的概念,这使得同一页面中的控件可以分组进行验证,A组的验证不会影响到B组按钮的提交,此外ASP.NET 2.0对内嵌资源文件的完美支持也使得验证框架对客户端验证所需脚本文件的管理更加的健壮,而不用像ASP.NET 1.x一样需要在IIS的根目录下添加脚本文件。
注:ASP.NET 1.x需要在IIS的根目录,比如:C:\inetpub\wwwroot下的\aspnet_client\ system_web\[.NET_Version]\子目录中添加用于客户端验证的脚本文件,验证控件才能正常工作。当你使用Windows Server 2003的IIS6或Vista的IIS7建立多个网站时,这意味着你要用.NET Framework根目录下的aspnet_regiis.exe工具多次注册这些脚本文件,或把这些文件从一个地方按对应的目录复制到另一个地方。
7.1.1 服务端验证
ASP.NET验证控件体系的核心是IValidator接口,验证控件体系中的所有控件都实现了这个接口,它声明了两个属性:ErrorMessage、IsValid和一个方法Validate()。
IsValid属性指示被验证控件的值是否通过验证,可调用Validate()方法计算验证条件,更新IsValid属性。ErrorMessage属性则用于设置验证失败时显示的信息。除了ErrorMessage,验证控件的Text属性(从Control基类继承的Text属性)通常也和显示的错误信息有关。这两个属性的不同之处在于,ErrorMessage一般用于显示在ValidationSummary控件中,而Text属性则由验证控件自己显示,如果Text属性为空,那么验证控件自己也显示ErrorMessage。
虽然验证控件并不一定用于验证某个控件的值(比如CustomValidator就可以不指定ControlToValidate属性),但ASP.NET框架中自带的所有验证控件都提供了ControlToValidate属性,而且除了CustomValidator,其它验证控件的ControlToValidate属性都必须设为一个装饰了ValidationPropertyAttribute的控件的ID。换句话说,一个控件要能被验证控件验证,它必须装饰上ValidationPropertyAttribute,比如TextBox控件:
[ValidationProperty("Text")]
public class TextBox : WebControl,IPostBackDataHandler,
IeditableTextControl,ITextControl
{
//… …
}
ValidationPropertyAttribute的Name属性决定验证控件的哪个属性。
7.1.2 客户端验证
ASP.NET验证控件的客户端验证功能基于一套完善而统一的客户端组件,如果希望对ASP.NET验证控件进行扩展,那么了解它的客户端验证框架非常重要。
首先,如果页面中存在验证控件,那么页面会添加一个内嵌脚本资源文件的引用,该文件包含客户端验证过程中需要的各种函数和各种基本验证的验证算法函数,包括ValidationSummary控件所需的功能。
然后,页面中的所有验证控件被组织成Page_Validators数组:
var Page_Validators = new Array(
document.getElementById("RequiredFieldValidator1"),
document.getElementById("RequiredFieldValidator2"),
document.getElementById("RequiredFieldValidator3"),
document.getElementById("CompareValidator2"),
document.getElementById("CompareValidator1"));
利用Javascript语言的动态特性,每个验证控件必须的属性被一一附加到验证控件呈现的DOM元素对象上:
var RequiredFieldValidator1 = document.all ?
document.all["RequiredFieldValidator1"] :
document.getElementById("RequiredFieldValidator1");
RequiredFieldValidator1.controltovalidate = "txtName";
RequiredFieldValidator1.errormessage = "请输入用户名";
RequiredFieldValidator1.validationGroup = "Register";
RequiredFieldValidator1.evaluationfunction =
"RequiredFieldValidatorEvaluateIsValid";
RequiredFieldValidator1.initialvalue = "";
以上代码中initialvalue属性为RequiredFiledValidator控件特有的属性,其他均为各种验证控件共有的属性。其中evaluationfunction属性指定进行验证运算的函数。实际执行验证时,RequiredFieldValidator1将作为参数传给RequiredFieldValidatorEvaluateIsValid()函数,验证函数通过RequiredFieldValidator1得到所有验证过程中需要的参数。在实现自定义的验证控件时,只需实现自己的验证计算函数,并将其指定为验证控件的evaluationfunciton属性即可定制客户端的验证行为。
然后,将调用引入的脚本资源文件中的ValidatorOnLoad()方法对所有验证控件进行初始化:
<script type="text/javascript">
<!--
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
ValidatorOnLoad();
}
function ValidatorOnSubmit() {
if (Page_ValidationActive) {
return ValidatorCommonOnSubmit();
}
else {
return true;
}
}
function WebForm_OnSubmit() {
if (typeof(ValidatorOnSubmit) == "function"
&& ValidatorOnSubmit() == false)
return false;
return true;
}
// -->
</script>
表单提交时将调用WebForm_OnSubmit()方法,并最终调用ValidatorCommonOnSubmit()方法:
<form name="form1" method="post" action="TestValidator.aspx"
onsubmit= "javascript:return WebForm_OnSubmit();" id="form1">
脚本资源文件中的ValidatorCommonOnSubmit()方法将执行页面所有验证控件或所有验证组属性相同的验证控件的验证计算过程。
值得注意的是页面中的表单提交按钮的onclick事件也被修改:
<input type="submit" name="btnRegister" value="注册" onclick="javascript: WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(‘btnRegister’,‘’,true, ‘Register’,‘’,false,false))" id="btnRegister" />
WebForm_DoPostBackWithOptions()函数定义在另一个更基础的脚本资源文件中,该文件定义了表单回传、回调以及维持页面滚动位置所需的方法以及一些工具性的函数,比如大名鼎鼎的用于回调的WebForm_DoCallback()函数,以及获得DOM对象位置的WebForm_ GetElementPosition()。
WebForm_DoPostBackWithOptions()函数支持7个参数,分别是: eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit,对比上面的示例代码,即可明白各参数的含义。
7.1.3 BaseValidator类
ASP.NET客户端验证框架也许让你觉得比较烦琐,要完全理解和把握它可能不是一时半会的事情,好在我们并不需要直接针对客户端验证接口编程。
ASP.NET框架为验证控件提供了基础的BaseValidator抽象类,BaseValidator继承自Label并实现了IValidator接口,而且提供了大量属性和方法,隐藏了验证框架的复杂性,如表7-1所列。
表7-1 BaseValidator属性
|
属 性 |
说 明 |
|
ControlToValidate |
设置被验证的控件。对应客户端控件的controltovalidate扩展属性,比如:RequiredFieldValidator1.controltovalidate = "txtName" |
|
Display |
验证控件错误信息的显示方式,None为始终不显示,Dynamic通过display属性控制信息的显示与隐藏,Static通过style.visibility属性控制信息的显示与隐藏 |
|
ErrorMessage |
错误信息文本 |
|
IsValid |
验证是否通过 |
|
EnableClientScript |
是否启用客户端验证 |
续表
|
属 性 |
说 明 |
|
SetFocusOnError |
自动将焦点置于未通过验证的控件内 |
|
ValidationGroup |
验证组 |
|
RenderUplever |
检测访问浏览器是否支持客户端验证功能 |
可见验证控件在一般情况下需要的属性在BaseValidator中都已提供。其中ControlToValidate属性装饰了IDReferencePropertyAttribute,指出该属性包含ID引用,因此IDE在设计时为其提供列出ID等服务,此外,TypeConverterAttribute指定属性关联ValidatedControlConverter,ValidatedControlConverter类型转换器继承自ControlIDConverter,并且其重载的FilterControl()方法过滤掉未应用ValidationPropertyAttribute的控件。
[TypeConverter(typeof(ValidatedControlConverter)), IDReferenceProperty]
public string ControlToValidate
{
get;
set;
}
BaseValidator控件还提供了大量函数,支持服务端验证,组织与客户端验证框架的交互。比如,GetControlValidationValue ()方法可用来得到验证控件的值,GetControlRenderID()可用来得到被验证控件的客户端ID。
BaseValidator控件的呈现过程比较复杂,在OnPreRender()方法中注册客户端验证所需的脚本资源文件和脚本,在AddAttributesToRender()方法中利用ClientScriptManager.AddExpando Attribute()方法为客户端控件添加扩展属性脚本,例如:
var RequiredFieldValidator1 = document.all ?
document.all["RequiredFieldValidator1"] :
document.getElementById("RequiredFieldValidator1");
RequiredFieldValidator1.controltovalidate = "txtName";
RequiredFieldValidator1.errormessage = "请输入用户名";
RequiredFieldValidator1.validationGroup = "Register";
RequiredFieldValidator1.evaluationfunction =
"RequiredFieldValidatorEvaluateIsValid";
RequiredFieldValidator1.initialvalue = "";
这些客户端的扩展属性将被用于客户端验证过程。
此外,BaseValidator的Render()方法利用ClientScriptManager.RegisterArrayDeclaration()方法生成页面中的Javascript数组——Page_Validators:
var Page_Validators = new Array(
document.getElementById("RequiredFieldValidator1"),
document.getElementById("RequiredFieldValidator2"),
document.getElementById("RequiredFieldValidator3"),
document.getElementById("CompareValidator2"),
document.getElementById("CompareValidator1"));
RegisterArrayDeclaration()方法可用于在不同的地方为同一个客户端数组组织数组项。比如上面的例子中,同一页面中的五个验证控件分别在各自的Render()方法中执行以下代码:
string arrayValue = "document.getElementById(\"" + this.ClientID + "\")";
this.Page.ClientScript.RegisterArrayDeclaration("Page_Validators",
arrayValue);
最终生成的是含有五个值的Page_Validators数组。
总而言之,除了验证计算逻辑,BaseValidator控件已经为我们准备好了所有的基础功能。







