创建Linked ValidationSummary, 深入理解ASP.NET的Validation

一 、实现的效果

我想对于ASP.NET的Validator控件已经熟悉的不能再熟悉了。我们 已经习惯了用Validator控件来验证我们在表单的输入,并通过ValidationSummary来输出我们为Validator控件设置的Error message。不知道大家有没想过进一步改进一下我们的Validation来改善我们的User Experience。比如,在ValidationSummary输出一个Link连接到对应的控件,而不是显示单纯的Error message。

 


比如在上图中,是一个典型的Login的Page。我们有两个必填的字段:User name和Password。为此我定义两个RequiredFieldValidator。他们的Error message分别为:”User name is mandatory!”和”Password is mandatory!”。在未输入任何值得前提下Click “Sign in”按钮,Error Message被显示在ValidationSummary上面。不过和传统的Error message不同,显示在ValidationSummary上的实际上是两个链接,Click对应的Error message,光标会设置到对应的Textbox上。比如上图所示:Click ”User name is mandatory!”,光标回到User name对应的Texbox。

二、具体实现

现在我们来简单叙述上面的效果是如果实现的,在开始之前我想说的是,方法非常简单—或许你已经猜到了:)

1.首先来看看aspx。

 

<% @ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login"  %>
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< html  xmlns ="http://www.w3.org/1999/xhtml" >
< head  id ="Head1"  runat ="server" >
    
< title > Login </ title >
    
< style  type ="text/css" >
        body
{font-family:Verdana; font-size:10px}
        table
{width:300px}
        table tr
{height:30px}
        table td.firstColumn
{width:100px; text-align:right}
        table td.secondColumn
{ text-align:left}
        table span.asterisk
{color:red}
        table .textbox
{width:150px; border:solid 1px #999999}
        table .button
{background-color: #00cc66;border:solid 1px #999999}
        ul li
{margin-bottom:5px} 
        ul li a
{color:red; text-decoration:none}
        ul li a:hover
{text-decoration:underline} 
   
</ style >
   
   
< script  type ="text/javascript" >
        
function setFocus(control)
       
{
           
var controlToValidate = document.getElementById(control);
           controlToValidate.focus();
       }
 
       
   
</ script >
</ head >
< body  style ="font-family: Verdana" >
    
< form  id ="form1"  runat ="server" >
        
< div >
            
< table  cellpadding ="0"  cellspacing ="5px" >
                
< tr >
                    
< td  colspan ="2" >
                        
< asp:ValidationSummary  runat ="server"  ID ="vldLogin"   />
                    
</ td >
                
</ tr >
                
< tr >
                    
< td  class ="firstColumn" >
                        User Name: 
< span  class ="asterisk" > &nbsp; * </ span ></ td >
                    
< td  class ="secondColumn" >
                        
< asp:TextBox  runat ="server"  ID ="txtUserName"  CssClass ="textbox" ></ asp:TextBox >
                        
< asp:RequiredFieldValidator  runat ="server"  ID ="rqfUserName"  ControlToValidate ="txtUserName"  Display ="None" ></ asp:RequiredFieldValidator >
                        
< asp:CustomValidator  runat ="server"  ID ="ctmUserName"   Display ="None"  OnServerValidate ="ctmUserName_ServerValidate"  ControlToValidate ="txtUserName"   ></ asp:CustomValidator >
                    
</ td >
                
</ tr >
                
< tr >
                    
< td  class ="firstColumn" >
                        Password: 
< span  class ="asterisk" > &nbsp; * </ span ></ td >
                    
< td  class ="secondColumn" >
                        
< asp:TextBox  runat ="server"  ID ="txtPassword"  TextMode ="Password"  CssClass ="textbox" ></ asp:TextBox >
                        
< asp:RequiredFieldValidator  runat ="server"  ID ="rqfPassword"  ControlToValidate ="txtPassword"  Display ="None"   ></ asp:RequiredFieldValidator >
                    
</ td >
                
</ tr >
                
< tr >
                    
< td  colspan ="2"  align ="center" >
                        
< asp:Button  runat ="server"  ID ="btnSignIn"  Text ="Sign in"  CssClass ="button"   /> &nbsp;&nbsp;&nbsp;
                        
< asp:Button  runat ="server"  ID ="ButtonCancel"  Text ="Cancel"  CausesValidation ="false"
                            CssClass
="button"   />
                    
</ td >
                
</ tr >
            
</ table >
        
</ div >
    
</ form >
</ body >
</ html >

在看到了上面的Screen shot之后再看看上面的Html,结构清晰得一目了然。所以我就不再进一步解释了。在这里我只需要提提定义在aspx的一段javascript function:setFocus。通过它把focus设置到指定的控件。

    < script  type ="text/javascript" >
        
function setFocus(control)
       
{
           
var controlToValidate = document.getElementById(control);
           controlToValidate.focus();
       }
       
   
</ script >

2.接着我们来看看code behind。

 

Code也简单得一塌糊涂,除了MakeClickableErrorMessage这个Method,其他的都不值一提。

 

private   void  MakeClickableErrorMessage()
    
{
        
foreach (BaseValidator validator in this.Validators)
        
{
            
if (validator.ControlToValidate == string.Empty)
            
{
                
continue;
            }

            
string clientID = this.FindControl(validator.ControlToValidate).ClientID;
            
string script = string.Format("<a href= /"javascript:setFocus('{0}');/">{1}</a>", clientID, validator.ErrorMessage);
            validator.ErrorMessage 
= script;
        }

    }

显示在ValidationSummary中原本简单的literal error message就是通过上面的这个MakeClickableErrorMessage转变成hyperlink的。在上面的code中,我遍历page中的每个Validator control。如果该Validator control有对应ControlToValidate(对于一个Validator control来说,ControlToValidate并非一个必需的property,如果没有指定该property,其值为空字符串),直接进入下一个循环。然后我把原来只是弹出的文本转变成一个<a></a>,然后再将其重新赋值给对应的Validator contorl的ErrorMessage property。

比如对于rqfUserName RequiredFieldValidator来说,原来的Error message是”User name is mandatory!”,那么现在的Error message变成了:

< href =”javascript:  setFocus(‘txtUserName’);” >  User name is mandatory! </ a >

三、ASP.NET是如何实现Validation的

上面只是一个简单的小窍门,我们以这个Sample为例,来进一步介绍ASP.NET如何尽心Validation的。为了简单起见,在这里我没法讨论所有的Validator control。只介绍RequiredFieldValidator和CustomValidator这两种Validator control的处理流程。

1.Client side Validation

我们通过IE来浏览上面的Page,通过参看Source code,可以看到最后Render出来的html:

 

我们从中提取对Validation有用的信息。

首先我们会看到有两个JavaScript被引用:

< script  src ="/Artech.ClickableValidationSummary/WebResource.axd?d=07ZNXubMk-rxUjn0jMywXg2&amp;t=632969324944906146"  type ="text/javascript" ></ script >
< script  src ="/Artech.ClickableValidationSummary/WebResource.axd?d=5q3WmDnqxzNvEfUc_QbMe5qdQO1LUQ4P7mwuv6CrIMk1&amp;t=632969324944906146"  type ="text/javascript" ></ script >

这两个JavaScript由ASP.NET生成。尤其内容较多,在这里先不列出他们的内容,等下面真正要使用到其中定义的JavaScript 在列出来。我们现在姑且称它们为JavaScript1和JavaScript2。

在下面一段JavaScript中,为3个Validator control定义了3个Client端的对象,对象的名称和控件名称同名,并设置相关的属性:controltovalidate,errormessage,display,evaluationfunction。其中evaluationfunction为进行Validation的function的名称.

< script  type ="text/javascript" >
<!--
var rqfUserName = document.all ? document.all["rqfUserName"] : document.getElementById("rqfUserName");
rqfUserName.controltovalidate 
= "txtUserName";
rqfUserName.errormessage 
= "<a href= /"javascript:setFocus(/'txtUserName/');/">User name is mandatory!</a>";
rqfUserName.display 
= "None";
rqfUserName.evaluationfunction 
= "RequiredFieldValidatorEvaluateIsValid";
rqfUserName.initialvalue 
= "";
var ctmUserName = document.all ? document.all["ctmUserName"] : document.getElementById("ctmUserName");
ctmUserName.controltovalidate 
= "txtUserName";
ctmUserName.errormessage 
= "<a href= /"javascript:setFocus(/'txtUserName/');/">Such a user has not registered!</a>";
ctmUserName.display 
= "None";
ctmUserName.evaluationfunction 
= "CustomValidatorEvaluateIsValid";
var rqfPassword = document.all ? document.all["rqfPassword"] : document.getElementById("rqfPassword");
rqfPassword.controltovalidate 
= "txtPassword";
rqfPassword.errormessage 
= "<a href= /"javascript:setFocus(/'txtPassword/');/">Password is mandatory!</a>";
rqfPassword.display 
= "None";
rqfPassword.evaluationfunction 
= "RequiredFieldValidatorEvaluateIsValid";
rqfPassword.initialvalue 
= "";
// -->
</ script >

我们还发现通过Javascript定义了两个Array对象:Page_ValidationSummaries和Page_Validators。这两个Array用于保存Page中的所有的ValidationSummary和Validator control。

 

< script  type ="text/javascript" >
<!--
var Page_ValidationSummaries =  new Array(document.getElementById("vldLogin"));
var Page_Validators =  new Array(document.getElementById("rqfUserName"), document.getElementById("ctmUserName"), document.getElementById("rqfPassword"));
// -->
</ script >

我们知道,所有的Validation操作都是在Click “Sign In” Button之后进行的。我们来看看,他是如何定义的:

 

< input  type ="submit"  name ="btnSignIn"  value ="Sign in"  onclick ="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;btnSignIn&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))"  id ="btnSignIn"  class ="button"   />

通过onclick事件,我们可以看到,一个命名为WebForm_DoPostBackWithOptions的javascript function被调用,该function接收一个称为WebForm_PostBackOptions类型的对象。该类型被定一个在JavaScript1中(还记得JavaScript1指的是什么吗? 上溯到第三段)。下面是他的定义:

function  WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit)  {
    
this.eventTarget = eventTarget;
    
this.eventArgument = eventArgument;
    
this.validation = validation;
    
this.validationGroup = validationGroup;
    
this.actionUrl = actionUrl;
    
this.trackFocus = trackFocus;
    
this.clientSubmit = clientSubmit;
}

该对象具有这样的表述的是关于Postback context的一些信息,比如:

  • eventTarget:Event触发的control,当前为” btnSignIn”。
  • eventArgument:Event额外的参数, 当前为””。
  • validation:是否进行Validation,当前为true。
  • validationGroup:eventTarget 对应的Validation group,这是ASP.NET 2.0的新特性,当当前为””,因为我没有设置btnSignIn的ValidationGroup的property。
  • actionUrl:表单被提交的Url,就像asp中Form的action一样。ASP.NET 1.x不提供cross-page的提交,在2.0中提供了此功能,当前为””, 我没有进行cross-page的提交。
  • trackFocus:是否进行焦点追踪,当前为false。
  • clientSubmit:是否通过form submit导致Postback,当前为false。

我们再来看看WebForm_DoPostBackWithOptions,像WebForm_PostBackOptions一样,该function同样被定义在JavaScript1中。

 

function  WebForm_DoPostBackWithOptions(options)  {
    
var validationResult = true;
    
if (options.validation) {
        
if (typeof(Page_ClientValidate) == 'function') {
            validationResult 
= Page_ClientValidate(options.validationGroup);
        }

    }

    
if (validationResult) {
        
if ((typeof(options.actionUrl) != "undefined"&& (options.actionUrl != null&& (options.actionUrl.length > 0)) {
            theForm.action 
= options.actionUrl;
        }

        
if (options.trackFocus) {
            
var lastFocus = theForm.elements["__LASTFOCUS"];
            
if ((typeof(lastFocus) != "undefined"&& (lastFocus != null)) {
                
if (typeof(document.activeElement) == "undefined"{
                    lastFocus.value 
= options.eventTarget;
                }

                
else {
                    
var active = document.activeElement;
                    
if ((typeof(active) != "undefined"&& (active != null)) {
                        
if ((typeof(active.id) != "undefined"&& (active.id != null&& (active.id.length > 0)) {
                            lastFocus.value 
= active.id;
                        }

                        
else if (typeof(active.name) != "undefined"{
                            lastFocus.value 
= active.name;
                        }

                    }

                }

            }

        }

    }

    
if (options.clientSubmit) {
        __doPostBack(options.eventTarget, options.eventArgument);
    }

}

在开始的时候,调用Page_ClientValidate进行Client端的Validation。在这里执行所有的Client端的Validation。我们来着重分析上面的javascript,看看具体的流程。Page_ClientValidate被定义在Javascript2中。

 

function  Page_ClientValidate(validationGroup)  {
    Page_InvalidControlToBeFocused 
= null;
    
if (typeof(Page_Validators) == "undefined"{
        
return true;
    }

    
var i;
    
for (i = 0; i < Page_Validators.length; i++{
        ValidatorValidate(Page_Validators[i], validationGroup, 
null);
    }

    ValidatorUpdateIsValid();
    ValidationSummaryOnSubmit(validationGroup);
    Page_BlockSubmit 
= !Page_IsValid;
    
return Page_IsValid;
}

上面的code中,首先通过Page_Validators判断是否Page中定义了Validator control。我们在预先定义了Page_Validators Array(还记得我们之前介绍的两个Array——Page_ValidationSummaries和Page_Validators吗?)。虽有遍历所有的Validator control,并调用ValidatorValidate方法执行每个Validator control的Client端的Validation。我们进一步看看ValidatorValidate又是如何定义的(ValidatorValidate定义在Javascript2中):

 

function  ValidatorValidate(val, validationGroup, event)  {
    val.isvalid 
= true;
    
if ((typeof(val.enabled) == "undefined" || val.enabled != false&& IsValidationGroupMatch(val, validationGroup)) {
        
if (typeof(val.evaluationfunction) == "function"{
            val.isvalid 
= val.evaluationfunction(val);
            
if (!val.isvalid && Page_InvalidControlToBeFocused == null &&
                
typeof(val.focusOnError) == "string" && val.focusOnError == "t"{
                ValidatorSetFocus(val, event);
            }

        }

    }

    ValidatorUpdateDisplay(val);
}

首先通过IsValidationGroupMatch判断Validator control的ValidationGroup是否和触发Postaback的Control对应的ValidationGroup相互匹配。因为只有在匹配的前提下才进行相关Validator control的validation。然后调用validator control的evaluationfunction function来进行validation。通过前面的分析,我们知道RequiredFieldValidator的evaluationfunction为RequiredFieldValidatorEvaluateIsValid,而CustomValidator的evaluationfunction为CustomValidatorEvaluateIsValid。我们来看看这两个function是如何定义的。他们都定义在Javascript2中。

  • RequiredFieldValidatorEvaluateIsValid:通过正则表达式验证是否填入了由意义的值。

 

function  RequiredFieldValidatorEvaluateIsValid(val)  {
    
return (ValidatorTrim(ValidatorGetValue(val.controltovalidate)) !=       ValidatorTrim(val.initialvalue))
}

function  ValidatorGetValue(id)  {
    
var control;
    control 
= document.getElementById(id);
    
if (typeof(control.value) == "string"{
        
return control.value;
    }

    
return ValidatorGetValueRecursive(control);
}

function  ValidatorGetValueRecursive(control)
{
    
if (typeof(control.value) == "string" && (control.type != "radio" || control.checked == true)) {
        
return control.value;
    }

    
var i, val;
    
for (i = 0; i<control.childNodes.length; i++{
        val 
= ValidatorGetValueRecursive(control.childNodes[i]);
        
if (val != ""return val;
    }

    
return "";
}

function  ValidatorTrim(s)  {
    
var m = s.match(/^/s*(/S+(/s+/S+)*)/s*$/);
    
return (m == null? "" : m[1];
}

  • CustomValidatorEvaluateIsValid:实际上就是调用我们为CustomValidator设置的ClientValidationFunction。

 

function  CustomValidatorEvaluateIsValid(val)  {
    
var value = "";
    
if (typeof(val.controltovalidate) == "string"{
        value 
= ValidatorGetValue(val.controltovalidate);
        
if ((ValidatorTrim(value).length == 0&&
            ((
typeof(val.validateemptytext) != "string"|| (val.validateemptytext != "true"))) {
            
return true;
        }

    }

    
var args = { Value:value, IsValid:true };
    
if (typeof(val.clientvalidationfunction) == "string"{
        eval(val.clientvalidationfunction 
+ "(val, args) ;");
    }

    
return args.IsValid;
}

在ValidatorValidate中,当我们通过调用各个Validator control的evaluationfunction来进行Client端的验证后,对于没有通过验证的Validator control,通过调用ValidatorSetFocus设置相应控件的焦点。在这里就不在深入探讨了。接着通过调用ValidatorUpdateDisplay来根据我们制定的Display和不同浏览器,来设置Error message的显示方式。

 

function  ValidatorUpdateDisplay(val)  {
    
if (typeof(val.display) == "string"{
        
if (val.display == "None"{
            
return;
        }

        
if (val.display == "Dynamic"{
            val.style.display 
= val.isvalid ? "none" : "inline";
            
return;
        }

    }

    
if ((navigator.userAgent.indexOf("Mac"> -1&&
        (navigator.userAgent.indexOf(
"MSIE"> -1)) {
        val.style.display 
= "inline";
    }

    val.style.visibility 
= val.isvalid ? "hidden" : "visible";
}

实际上到现在为止,所有的Validation工作已经完成。我们来看看Error message是如何显示的。所以我们要看看ValidatorUpdateDisplay的定义了。

分析完ValidatorValidate,我们在回到Page_ClientValidate上面。现在我们接着分析一下的执行流程。通过调用ValidatorValidate执行完各个Validator control的验证后,接着调用的是ValidatorUpdateIsValid()和ValidationSummaryOnSubmit(validationGroup)。ValidatorUpdateIsValid通过遍历每个Validator control来查看他们是否通过验证,最终确定这个Page是否通过验证。ValidationSummaryOnSubmit通过拼接字符串的形式在ValidationSummary显示对应的Error message。这正是我们可我们Error message写成hyperlink的原因所在。

function  ValidatorUpdateIsValid()  {
    Page_IsValid 
= AllValidatorsValid(Page_Validators);
}

function  AllValidatorsValid(validators)  {
    
if ((typeof(validators) != "undefined"&& (validators != null)) {
        
var i;
        
for (i = 0; i < validators.length; i++{
            
if (!validators[i].isvalid) {
                
return false;
            }

        }

    }

    
return true;
}

function  ValidationSummaryOnSubmit(validationGroup)  {
    
if (typeof(Page_ValidationSummaries) == "undefined")
        
return;
    
var summary, sums, s;
    
for (sums = 0; sums < Page_ValidationSummaries.length; sums++{
        summary 
= Page_ValidationSummaries[sums];
        summary.style.display 
= "none";
        
if (!Page_IsValid && IsValidationGroupMatch(summary, validationGroup)) {
            
var i;
            
if (summary.showsummary != "False"{
                summary.style.display 
= "";
                
if (typeof(summary.displaymode) != "string"{
                    summary.displaymode 
= "BulletList";
                }

                
switch (summary.displaymode) {
                    
case "List":
                        headerSep 
= "<br>";
                        first 
= "";
                        pre 
= "";
                        post 
= "<br>";
                        end 
= "";
                        
break;
                    
case "BulletList":
                    
default:
                        headerSep 
= "";
                        first 
= "<ul>";
                        pre 
= "<li>";
                        post 
= "</li>";
                        end 
= "</ul>";
                        
break;
                    
case "SingleParagraph":
                        headerSep 
= " ";
                        first 
= "";
                        pre 
= "";
                        post 
= " ";
                        end 
= "<br>";
                        
break;
                }

                s 
= "";
                
if (typeof(summary.headertext) == "string"{
                    s 
+= summary.headertext + headerSep;
                }

                s 
+= first;
                
for (i=0; i<Page_Validators.length; i++{
                    
if (!Page_Validators[i].isvalid && typeof(Page_Validators[i].errormessage) == "string"{
                        s 
+= pre + Page_Validators[i].errormessage + post;
                    }

                }

                s 
+= end;
                summary.innerHTML 
= s;
                window.scrollTo(
0,0);
            }

            
if (summary.showmessagebox == "True"{
                s 
= "";
                
if (typeof(summary.headertext) == "string"{
                    s 
+= summary.headertext + "/r/n";
                }

                
var lastValIndex = Page_Validators.length - 1;
                
for (i=0; i<=lastValIndex; i++{
                    
if (!Page_Validators[i].isvalid && typeof(Page_Validators[i].errormessage) == "string"{
                        
switch (summary.displaymode) {
                            
case "List":
                                s 
+= Page_Validators[i].errormessage;
                                
if (i < lastValIndex) {
                                    s 
+= "/r/n";
                                }

                                
break;
                            
case "BulletList":
                            
default:
                                s 
+= "" + Page_Validators[i].errormessage;
                                
if (i < lastValIndex) {
                                    s 
+= "/r/n";
                                }

                                
break;
                            
case "SingleParagraph":
                                s 
+= Page_Validators[i].errormessage + " ";
                                
break;
                        }

                    }

                }

                alert(s);
            }

        }

    }

}

2 Server side Validation

前面我们花了很大的篇幅介绍了Client端的Validation,通过介绍我们知道了,Client端的validation和Error message的显示均由Javascript来完成。现在我们来简单看看Server 端的Validation。当client 端__doPostBack被调用实现向Server端的Postback。具体的Postback可以参考我的文章:浅谈ASP.NET的Postback。Validator Contro 的Server端的Validation,Error message直接通过Html显示出来。

比如下面是一段CustomValidator的Validation。

protected   void  ctmUserName_ServerValidate( object  source, ServerValidateEventArgs args)
    
{
        
if (this.txtUserName.Text.Trim() != "adm")
        
{
            args.IsValid 
= false;
            
return;
        }

        args.IsValid 
= true;
    }

如果上面的Validation没有通过,最终render在client段的将有下面一段Html。



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值