ASP.NET:创建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上。比如上图所示:单击”User name is mandatory!”,光标回到User name对应的Texbox

一、首先来看看aspx

现在我们来简单叙述上面的效果是如果实现的,在开始之前我想说的是,方法非常简单—或许你已经猜到了。下面是上面创建的用于登录的Web页面的HTML。

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %>
   2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3: <html xmlns="http://www.w3.org/1999/xhtml">
   4: <head id="Head1" runat="server">
   5:     <title>Login</title>
   6:     <style type="text/css">
   7:         body{}{font-family:Verdana; font-size:10px}
   8:         table{}{width:300px}
   9:         table tr{}{height:30px}
  10:         table td.firstColumn{}{width:100px; text-align:right}
  11:         table td.secondColumn{}{ text-align:left}
  12:         table span.asterisk{}{color:red}
  13:         table .textbox{}{width:150px; border:solid 1px #999999}
  14:         table .button{}{background-color: #00cc66;border:solid 1px #999999}
  15:         ul li{}{margin-bottom:5px} 
  16:         ul li a{}{color:red; text-decoration:none}
  17:         ul li a:hover{}{text-decoration:underline} 
  18:    </style>
  19:    
  20:    <script type="text/javascript">
   1:  
   2:        function setFocus(control) {
   3:            var controlToValidate = document.getElementById(control);
   4:            controlToValidate.focus();
   5:        } 
   6:        
   7:    
</ script >
  21: </head>
  22: <body style="font-family: Verdana">
  23:     <form id="form1" runat="server">
  24:         <div>
  25:             <table cellpadding="0" cellspacing="5px">
  26:                 <tr>
  27:                     <td colspan="2">
  28:                         <asp:ValidationSummary runat="server" ID="vldLogin" />
  29:                     </td>
  30:                 </tr>
  31:                 <tr>
  32:                     <td class="firstColumn">
  33:                         User Name: <span class="asterisk">&nbsp;*</span></td>
  34:                     <td class="secondColumn">
  35:                         <asp:TextBox runat="server" ID="txtUserName" CssClass="textbox"></asp:TextBox>
  36:                         <asp:RequiredFieldValidator runat="server" ID="rqfUserName" ControlToValidate="txtUserName" Display="None"></asp:RequiredFieldValidator>
  37:                         <asp:CustomValidator runat="server" ID="ctmUserName"  Display="None" OnServerValidate="ctmUserName_ServerValidate" ControlToValidate="txtUserName" ></asp:CustomValidator>
  38:                     </td>
  39:                 </tr>
  40:                 <tr>
  41:                     <td class="firstColumn">
  42:                         Password: <span class="asterisk">&nbsp;*</span></td>
  43:                     <td class="secondColumn">
  44:                         <asp:TextBox runat="server" ID="txtPassword" TextMode="Password" CssClass="textbox"></asp:TextBox>
  45:                         <asp:RequiredFieldValidator runat="server" ID="rqfPassword" ControlToValidate="txtPassword" Display="None" ></asp:RequiredFieldValidator>
  46:                     </td>
  47:                 </tr>
  48:                 <tr>
  49:                     <td colspan="2" align="center">
  50:                         <asp:Button runat="server" ID="btnSignIn" Text="Sign in" CssClass="button" />&nbsp;&nbsp;&nbsp;
  51:                         <asp:Button runat="server" ID="ButtonCancel" Text="Cancel" CausesValidation="false"
  52:                             CssClass="button" />
  53:                     </td>
  54:                 </tr>
  55:             </table>
  56:         </div>
  57:     </form>
  58: </body>
  59: </html>

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

   1: <script type="text/javascript">
   2:        function setFocus(control)
   3:        {
   4:            var controlToValidate = document.getElementById(control);
   5:            controlToValidate.focus();
   6:        }       
   7: </script>

二、接着我们来看看后台代码

看完了HTML,我们来看看该登录Web页面的后台代码。下面的代码片断为你展示了该Web页面背后的所有代码,所有的机关就存在于Web页面的Load时间处理方法Page_Load方法中。

   1: public partial class Login : System.Web.UI.Page
   2: {
   3:     protected void Page_Load(object sender, EventArgs e)
   4:     {
   5:         if (this.IsPostBack)
   6:         {
   7:             return;
   8:         }
   9:  
  10:         this.rqfUserName.ErrorMessage = string.Format("{0} is mandatory!", "User name");
  11:         this.rqfPassword.ErrorMessage = string.Format("{0} is mandatory!", "Password");
  12:         this.ctmUserName.ErrorMessage = "Such a user has not registered!";
  13:  
  14:         this.MakeClickableErrorMessage();
  15:     }
  16:  
  17:     private void MakeClickableErrorMessage()
  18:     {
  19:         foreach (BaseValidator validator in this.Validators)
  20:         {
  21:             if (validator.ControlToValidate == string.Empty)
  22:             {
  23:                 continue;
  24:             }
  25:             string clientID = this.FindControl(validator.ControlToValidate).ClientID;
  26:             string script = string.Format("<a href= \"javascript:setFocus('{0}');\">{1}</a>", clientID, validator.ErrorMessage);
  27:             validator.ErrorMessage = script;
  28:         }
  29:     }
  30:  
  31:     protected void ctmUserName_ServerValidate(object source, ServerValidateEventArgs args)
  32:     {
  33:         if (this.txtUserName.Text.Trim() != "adm")
  34:         {
  35:             args.IsValid = false;
  36:             return;
  37:         }
  38:  
  39:         args.IsValid = true;
  40:     }
  41: }

上面代码也简单得一塌糊涂,除了MakeClickableErrorMessage这个方法,其他的都不值一提。显示在ValidationSummary中原本简单的literal error message就是通过上面的这个MakeClickableErrorMessage方法转变成超链接的。

   1: private void MakeClickableErrorMessage()
   2:     {
   3:         foreach (BaseValidator validator in this.Validators)
   4:         {
   5:             if (validator.ControlToValidate == string.Empty)
   6:             {
   7:                 continue;
   8:             }
   9:             string clientID = this.FindControl(validator.ControlToValidate).ClientID;
  10:             string script = string.Format("<a href= \"javascript:setFocus('{0}');\">{1}</a>", clientID, validator.ErrorMessage);
  11:             validator.ErrorMessage = script;
  12:         }
  13:     }

在上面的代码中,我遍历page中的每个验证控件。如果该验证具有对应ControlToValidate属性(对于一个验证控件来说,ControlToValidate并非一个必需的属性,如果没有指定该属性,其值为空字符串),直接进入下一个循环。然后我把原来只是弹出的文本转变成一个<a></a>块,然后再将其重新赋值给对应的Validator contorl的ErrorMessage property。比如对于rqfUserName RequiredFieldValidator来说,原来的Error message是”User name is mandatory!”,那么现在的Error message变成了:

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

上面只是一个简单的小窍门,我们以此为例,来进一步介绍ASP.NET如何进行验证的的。为了简单起见,在这里我没法讨论所有的验证控件。只介绍RequiredFieldValidatorCustomValidator这两种验证控件的处理流程。

三、ASP.NET是如何实现客户端验证的?

我们通过IE来浏览上面的Page,我们可以通过查看源文件得到最终呈现出来的Web页面的HTML。对于上面提到的登录页面,在客户端呈现出来的HTML如下面的HTML所示。

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
   2: <html xmlns="http://www.w3.org/1999/xhtml"> 
   3: <head id="Head1"><title> 
   4:     Login 
   5: </title> 
   6:     <style type="text/css"> 
   7:         body{}{font-family:Verdana; font-size:10px} 
   8:         table{}{width:300px} 
   9:         table tr{}{height:30px} 
  10:         table td.firstColumn{}{width:100px; text-align:right} 
  11:         table td.secondColumn{}{ text-align:left} 
  12:         table span.asterisk{}{color:red} 
  13:         table .textbox{}{width:150px; border:solid 1px #999999} 
  14:         table .button{}{background-color: #00cc66;border:solid 1px #999999} 
  15:         ul li{}{margin-bottom:5px}  
  16:         ul li a{}{color:red; text-decoration:none} 
  17:         ul li a:hover{}{text-decoration:underline}  
  18:    </style>    
  19:    <script type="text/javascript">
   1:  
   2:        function setFocus(control) {
   3:            var controlToValidate = document.getElementById(control);
   4:            controlToValidate.focus();
   5:        }        
   6:    
</ script >
   1:  
   2: </head> 
   3: <body style="font-family: Verdana"> 
   4:     <form name="form1" method="post" action="Login.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="form1"> 
   5: <div> 
   6: <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" /> 
   7: <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" /> 
   8: <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTg3OTM1NTM2MA9kFgICAw9kFgYCBQ8PFgIeDEVycm9yTWVzc2FnZQVKPGEgaHJlZj0gImphdmFzY3JpcHQ6c2V0Rm9jdXMoJ3R4dFVzZXJOYW1lJyk7Ij5Vc2VyIG5hbWUgaXMgbWFuZGF0b3J5ITwvYT5kZAIHDw8WAh8ABVI8YSBocmVmPSAiamF2YXNjcmlwdDpzZXRGb2N1cygndHh0VXNlck5hbWUnKTsiPlN1Y2ggYSB1c2VyIGhhcyBub3QgcmVnaXN0ZXJlZCE8L2E+ZGQCCw8PFgIfAAVJPGEgaHJlZj0gImphdmFzY3JpcHQ6c2V0Rm9jdXMoJ3R4dFBhc3N3b3JkJyk7Ij5QYXNzd29yZCBpcyBtYW5kYXRvcnkhPC9hPmRkZLFuksmAaQ+N5sw8K+rkFk3GqgSn" /> 
   9: </div> 
  10: <script type="text/javascript"> 
  11: <!--
  12:     var theForm = document.forms['form1'];
  13:     if (!theForm) {
  14:         theForm = document.form1;
  15:     }
  16:     function __doPostBack(eventTarget, eventArgument) {
  17:         if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
  18:             theForm.__EVENTTARGET.value = eventTarget;
  19:             theForm.__EVENTARGUMENT.value = eventArgument;
  20:             theForm.submit();
  21:         }
  22:     } 
  23: // --> 
</ script >
   1:  
   2: <script src="/Artech.ClickableValidationSummary/WebResource.axd?d=07ZNXubMk-rxUjn0jMywXg2&amp;t=632969324944906146" type="text/javascript">
   1: </script> 
   2: <script src="/Artech.ClickableValidationSummary/WebResource.axd?d=5q3WmDnqxzNvEfUc_QbMe5qdQO1LUQ4P7mwuv6CrIMk1&amp;t=632969324944906146" type="text/javascript">
   1: </script> 
   2: <script type="text/javascript"> 
   3: <!--
   4:     function WebForm_OnSubmit() {
   5:         if (typeof (ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;
   6:         return true;
   7:     } 
   8: // --> 
</ script >
   1:  
   2:         <div> 
   3:             <table cellpadding="0" cellspacing="5px"> 
   4:                 <tr> 
   5:                     <td colspan="2"> 
   6:                         <div id="vldLogin" style="color:Red;display:none;"> 
   7: </div> 
   8:                     </td> 
   9:                 </tr> 
  10:                 <tr> 
  11:                     <td class="firstColumn"> 
  12:                         User Name: <span class="asterisk">&nbsp;*</span></td> 
  13:                     <td class="secondColumn"> 
  14:                         <input name="txtUserName" type="text" id="txtUserName" class="textbox" /> 
  15:                         <span id="rqfUserName" style="color:Red;display:none;"></span> 
  16:                         <span id="ctmUserName" style="color:Red;display:none;"></span> 
  17:                     </td> 
  18:                 </tr> 
  19:                 <tr> 
  20:                     <td class="firstColumn"> 
  21:                         Password: <span class="asterisk">&nbsp;*</span></td> 
  22:                     <td class="secondColumn"> 
  23:                         <input name="txtPassword" type="password" id="txtPassword" class="textbox" /> 
  24:                         <span id="rqfPassword" style="color:Red;display:none;"></span> 
  25:                     </td> 
  26:                 </tr> 
  27:                 <tr> 
  28:                     <td colspan="2" align="center"> 
  29:                         <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" />&nbsp;&nbsp;&nbsp; 
  30:                         <input type="submit" name="ButtonCancel" value="Cancel" id="ButtonCancel" class="button" /> 
  31:                     </td> 
  32:                 </tr> 
  33:             </table> 
  34:         </div>     
  35: <script type="text/javascript"> 
  36: <!--
  37:     var Page_ValidationSummaries = new Array(document.getElementById("vldLogin"));
  38:     var Page_Validators = new Array(document.getElementById("rqfUserName"), document.getElementById("ctmUserName"), document.getElementById("rqfPassword")); 
  39: // --> 
</ script >
   1:  
   2:  
   3: <script type="text/javascript"> 
   4: <!--
   5:     var rqfUserName = document.all ? document.all["rqfUserName"] : document.getElementById("rqfUserName");
   6:     rqfUserName.controltovalidate = "txtUserName";
   7:     rqfUserName.errormessage = "<a href= \"javascript:setFocus(\'txtUserName\');\">User name is mandatory!</a>";
   8:     rqfUserName.display = "None";
   9:     rqfUserName.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
  10:     rqfUserName.initialvalue = "";
  11:     var ctmUserName = document.all ? document.all["ctmUserName"] : document.getElementById("ctmUserName");
  12:     ctmUserName.controltovalidate = "txtUserName";
  13:     ctmUserName.errormessage = "<a href= \"javascript:setFocus(\'txtUserName\');\">Such a user has not registered!</a>";
  14:     ctmUserName.display = "None";
  15:     ctmUserName.evaluationfunction = "CustomValidatorEvaluateIsValid";
  16:     var rqfPassword = document.all ? document.all["rqfPassword"] : document.getElementById("rqfPassword");
  17:     rqfPassword.controltovalidate = "txtPassword";
  18:     rqfPassword.errormessage = "<a href= \"javascript:setFocus(\'txtPassword\');\">Password is mandatory!</a>";
  19:     rqfPassword.display = "None";
  20:     rqfPassword.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
  21:     rqfPassword.initialvalue = ""; 
  22: // --> 
</ script >
   1:  
   2: <div> 
   3:     <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBQL7uOMiAqXVsrMJArWptJELAsP3i5QHAv23gdwNI0m2v8hOJGGPTPLYqDLAkZE0nKU=" /> 
   4: </div> 
   5: <script type="text/javascript"> 
   6: <!--
   7:     var Page_ValidationActive = false;
   8:     if (typeof (ValidatorOnLoad) == "function") {
   9:         ValidatorOnLoad();
  10:     }
  11:  
  12:     function ValidatorOnSubmit() {
  13:         if (Page_ValidationActive) {
  14:             return ValidatorCommonOnSubmit();
  15:         }
  16:         else {
  17:             return true;
  18:         }
  19:     } 
  20: // --> 
</ script >
  20:         </form> 
  21: </body> 
  22: </html> 

我们从中提取对验证有用的信息。首先我们会看到有两个JavaScript被引用:

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

这两个JavaScript由ASP.NET生成。尤其内容较多,在这里先不列出他们的内容,等下面真正要使用到其中定义的JavaScript 在列出来。我们现在姑且称它们为JavaScript1JavaScript2。在下面一段JavaScript中,为3个验证控件定义了3个客户端的对象,对象的名称和控件名称同名。并设置相关的属性:controltovalidate,errormessage,display,evaluationfunction。其中evaluationfunction为进行Validation的function的名称。

   1: <script type="text/javascript">
   2: <!--
   3:     var rqfUserName = document.all ? document.all["rqfUserName"] : document.getElementById("rqfUserName");
   4:     rqfUserName.controltovalidate = "txtUserName";
   5:     rqfUserName.errormessage = "<a href= \"javascript:setFocus(\'txtUserName\');\">User name is mandatory!</a>";
   6:     rqfUserName.display = "None";
   7:     rqfUserName.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
   8:     rqfUserName.initialvalue = "";
   9:     var ctmUserName = document.all ? document.all["ctmUserName"] : document.getElementById("ctmUserName");
  10:     ctmUserName.controltovalidate = "txtUserName";
  11:     ctmUserName.errormessage = "<a href= \"javascript:setFocus(\'txtUserName\');\">Such a user has not registered!</a>";
  12:     ctmUserName.display = "None";
  13:     ctmUserName.evaluationfunction = "CustomValidatorEvaluateIsValid";
  14:     var rqfPassword = document.all ? document.all["rqfPassword"] : document.getElementById("rqfPassword");
  15:     rqfPassword.controltovalidate = "txtPassword";
  16:     rqfPassword.errormessage = "<a href= \"javascript:setFocus(\'txtPassword\');\">Password is mandatory!</a>";
  17:     rqfPassword.display = "None";
  18:     rqfPassword.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
  19:     rqfPassword.initialvalue = "";
  20: // -->
  21: </script>

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

   1: <script type="text/javascript">
   2: <!--
   3: var Page_ValidationSummaries =  new Array(document.getElementById("vldLogin"));
   4: var Page_Validators =  new Array(document.getElementById("rqfUserName"), document.getElementById("ctmUserName"), document.getElementById("rqfPassword"));
   5: // -->
   6: </script>

我们知道,所有的Validation操作都是在Click “Sign In” 按钮之后进行的。我们来看看,该按钮的onClick事件处理程序是如何定义的:

< 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指的是什么吗? 上溯到第三段)。

   1: function WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit) {
   2:     this.eventTarget = eventTarget;
   3:     this.eventArgument = eventArgument;
   4:     this.validation = validation;
   5:     this.validationGroup = validationGroup;
   6:     this.actionUrl = actionUrl;
   7:     this.trackFocus = trackFocus;
   8:     this.clientSubmit = clientSubmit;
   9: }

该对象具有这样的表述的是关于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中。

   1: function WebForm_DoPostBackWithOptions(options) {
   2:     var validationResult = true;
   3:     if (options.validation) {
   4:         if (typeof(Page_ClientValidate) == 'function') {
   5:             validationResult = Page_ClientValidate(options.validationGroup);
   6:         }
   7:     }
   8:     if (validationResult) {
   9:         if ((typeof(options.actionUrl) != "undefined") && (options.actionUrl != null) && (options.actionUrl.length > 0)) {
  10:             theForm.action = options.actionUrl;
  11:         }
  12:         if (options.trackFocus) {
  13:             var lastFocus = theForm.elements["__LASTFOCUS"];
  14:             if ((typeof(lastFocus) != "undefined") && (lastFocus != null)) {
  15:                 if (typeof(document.activeElement) == "undefined") {
  16:                     lastFocus.value = options.eventTarget;
  17:                 }
  18:                 else {
  19:                     var active = document.activeElement;
  20:                     if ((typeof(active) != "undefined") && (active != null)) {
  21:                         if ((typeof(active.id) != "undefined") && (active.id != null) && (active.id.length > 0)) {
  22:                             lastFocus.value = active.id;
  23:                         }
  24:                         else if (typeof(active.name) != "undefined") {
  25:                             lastFocus.value = active.name;
  26:                         }
  27:                     }
  28:                 }
  29:             }
  30:         }
  31:     }
  32:     if (options.clientSubmit) {
  33:         __doPostBack(options.eventTarget, options.eventArgument);
  34:     }
  35: }

在开始的时候,调用Page_ClientValidate进行客户端验证。我们来着重分析上面的javascript,看看具体的流程。Page_ClientValidate被定义在Javascript2中。

   1: function Page_ClientValidate(validationGroup) {
   2:     Page_InvalidControlToBeFocused = null;
   3:     if (typeof(Page_Validators) == "undefined") {
   4:         return true;
   5:     }
   6:     var i;
   7:     for (i = 0; i < Page_Validators.length; i++) {
   8:         ValidatorValidate(Page_Validators[i], validationGroup, null);
   9:     }
  10:     ValidatorUpdateIsValid();
  11:     ValidationSummaryOnSubmit(validationGroup);
  12:     Page_BlockSubmit = !Page_IsValid;
  13:     return Page_IsValid;
  14: }

上面的代码中,首先通过Page_Validators判断Page是否存在验证控件。我们在预先定义了Page_Validators 数组(还记得我们之前介绍的两个Array——Page_ValidationSummaries和Page_Validators吗?)。虽有遍历所有的验证控件,并调用ValidatorValidate方法执行每个验证控件的客户端验证。我们进一步看看ValidatorValidate又是如何定义的(ValidatorValidate定义在Javascript2中)。

   1: function ValidatorValidate(val, validationGroup, event) {
   2:     val.isvalid = true;
   3:     if ((typeof(val.enabled) == "undefined" || val.enabled != false) && IsValidationGroupMatch(val, validationGroup)) {
   4:         if (typeof(val.evaluationfunction) == "function") {
   5:             val.isvalid = val.evaluationfunction(val);
   6:             if (!val.isvalid && Page_InvalidControlToBeFocused == null &&
   7:                 typeof(val.focusOnError) == "string" && val.focusOnError == "t") {
   8:                 ValidatorSetFocus(val, event);
   9:             }
  10:         }
  11:     }
  12:     ValidatorUpdateDisplay(val);
  13: }

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

  • RequiredFieldValidatorEvaluateIsValid:通过正则表达式验证是否具有有效输入
       1: function RequiredFieldValidatorEvaluateIsValid(val) {
       2:     return (ValidatorTrim(ValidatorGetValue(val.controltovalidate)) !=       ValidatorTrim(val.initialvalue))
       3: }
       4: function ValidatorGetValue(id) {
       5:     var control;
       6:     control = document.getElementById(id);
       7:     if (typeof(control.value) == "string") {
       8:         return control.value;
       9:     }
      10:     return ValidatorGetValueRecursive(control);
      11: }
      12: function ValidatorGetValueRecursive(control)
      13: {
      14:     if (typeof(control.value) == "string" && (control.type != "radio" || control.checked == true)) {
      15:         return control.value;
      16:     }
      17:     var i, val;
      18:     for (i = 0; i<control.childNodes.length; i++) {
      19:         val = ValidatorGetValueRecursive(control.childNodes[i]);
      20:         if (val != "") return val;
      21:     }
      22:     return "";
      23: }
      24: function ValidatorTrim(s) {
      25:     var m = s.match(/^\s*(\S+(\s+\S+)*)\s*$/);
      26:     return (m == null) ? "" : m[1];
      27: }

  • CustomValidatorEvaluateIsValid:实际上就是调用我们为CustomValidator设置的ClientValidationFunction
       1: function CustomValidatorEvaluateIsValid(val) {
       2:     var value = "";
       3:     if (typeof(val.controltovalidate) == "string") {
       4:         value = ValidatorGetValue(val.controltovalidate);
       5:         if ((ValidatorTrim(value).length == 0) &&
       6:             ((typeof(val.validateemptytext) != "string") || (val.validateemptytext != "true"))) {
       7:             return true;
       8:         }
       9:     }
      10:     var args = { Value:value, IsValid:true };
      11:     if (typeof(val.clientvalidationfunction) == "string") {
      12:         eval(val.clientvalidationfunction + "(val, args) ;");
      13:     }
      14:     return args.IsValid;
      15: }

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

   1: function ValidatorUpdateDisplay(val) {
   2:     if (typeof(val.display) == "string") {
   3:         if (val.display == "None") {
   4:             return;
   5:         }
   6:         if (val.display == "Dynamic") {
   7:             val.style.display = val.isvalid ? "none" : "inline";
   8:             return;
   9:         }
  10:     }
  11:     if ((navigator.userAgent.indexOf("Mac") > -1) &&
  12:         (navigator.userAgent.indexOf("MSIE") > -1)) {
  13:         val.style.display = "inline";
  14:     }
  15:     val.style.visibility = val.isvalid ? "hidden" : "visible";
  16: }

实际上到现在为止,所有的验证工作已经完成。接下来我们来看看验证失败后相应的错误消息是如何显示的。所以我们要看看ValidatorUpdateDisplay的定义了。

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

   1: function ValidatorUpdateIsValid() {
   2:     Page_IsValid = AllValidatorsValid(Page_Validators);
   3: }
   4: function AllValidatorsValid(validators) {
   5:     if ((typeof(validators) != "undefined") && (validators != null)) {
   6:         var i;
   7:         for (i = 0; i < validators.length; i++) {
   8:             if (!validators[i].isvalid) {
   9:                 return false;
  10:             }
  11:         }
  12:     }
  13:     return true;
  14: }
  15: function ValidationSummaryOnSubmit(validationGroup) {
  16:     if (typeof(Page_ValidationSummaries) == "undefined")
  17:         return;
  18:     var summary, sums, s;
  19:     for (sums = 0; sums < Page_ValidationSummaries.length; sums++) {
  20:         summary = Page_ValidationSummaries[sums];
  21:         summary.style.display = "none";
  22:         if (!Page_IsValid && IsValidationGroupMatch(summary, validationGroup)) {
  23:             var i;
  24:             if (summary.showsummary != "False") {
  25:                 summary.style.display = "";
  26:                 if (typeof(summary.displaymode) != "string") {
  27:                     summary.displaymode = "BulletList";
  28:                 }
  29:                 switch (summary.displaymode) {
  30:                     case "List":
  31:                         headerSep = "<br>";
  32:                         first = "";
  33:                         pre = "";
  34:                         post = "<br>";
  35:                         end = "";
  36:                         break;
  37:                     case "BulletList":
  38:                     default:
  39:                         headerSep = "";
  40:                         first = "<ul>";
  41:                         pre = "<li>";
  42:                         post = "</li>";
  43:                         end = "</ul>";
  44:                         break;
  45:                     case "SingleParagraph":
  46:                         headerSep = " ";
  47:                         first = "";
  48:                         pre = "";
  49:                         post = " ";
  50:                         end = "<br>";
  51:                         break;
  52:                 }
  53:                 s = "";
  54:                 if (typeof(summary.headertext) == "string") {
  55:                     s += summary.headertext + headerSep;
  56:                 }
  57:                 s += first;
  58:                 for (i=0; i<Page_Validators.length; i++) {
  59:                     if (!Page_Validators[i].isvalid && typeof(Page_Validators[i].errormessage) == "string") {
  60:                         s += pre + Page_Validators[i].errormessage + post;
  61:                     }
  62:                 }
  63:                 s += end;
  64:                 summary.innerHTML = s;
  65:                 window.scrollTo(0,0);
  66:             }
  67:             if (summary.showmessagebox == "True") {
  68:                 s = "";
  69:                 if (typeof(summary.headertext) == "string") {
  70:                     s += summary.headertext + "\r\n";
  71:                 }
  72:                 var lastValIndex = Page_Validators.length - 1;
  73:                 for (i=0; i<=lastValIndex; i++) {
  74:                     if (!Page_Validators[i].isvalid && typeof(Page_Validators[i].errormessage) == "string") {
  75:                         switch (summary.displaymode) {
  76:                             case "List":
  77:                                 s += Page_Validators[i].errormessage;
  78:                                 if (i < lastValIndex) {
  79:                                     s += "\r\n";
  80:                                 }
  81:                                 break;
  82:                             case "BulletList":
  83:                             default:
  84:                                 s += "- " + Page_Validators[i].errormessage;
  85:                                 if (i < lastValIndex) {
  86:                                     s += "\r\n";
  87:                                 }
  88:                                 break;
  89:                             case "SingleParagraph":
  90:                                 s += Page_Validators[i].errormessage + " ";
  91:                                 break;
  92:                         }
  93:                     }
  94:                 }
  95:                 alert(s);
  96:             }
  97:         }
  98:     }
  99: }

四、ASP.NET是如何实现服务端验证的?

前面我们花了很大的篇幅介绍了客户端验证,通过介绍我们知道了,客户端验证和错误消息的显示均由Javascript来完成。现在我们来简单看看服务端验证。客户端通过调用__doPostBack实现向服务端的Postback(具体的Postback可以参考我的文章:《浅谈ASP.NET的Postback》),并进行验证控件的服务端验证,错误消息直接通过Html显示出来。比如下面是一段CustomValidator的验证逻辑。

   1: protected void ctmUserName_ServerValidate(object source, ServerValidateEventArgs args)
   2:     {
   3:         if (this.txtUserName.Text.Trim() != "adm")
   4:         {
   5:             args.IsValid = false;
   6:             return;
   7:         }
   8:         args.IsValid = true;
   9:     }

如果上面的验证没有通过,最终呈现在客户端浏览器上的将如下图所示,后面是显示验证错误消息的HTML。
 


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
6月前
|
存储 开发框架 前端开发
asp.net与asp.net优缺点及示例
asp.net与asp.net优缺点及示例
|
29天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
28 0
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
60 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
95 5
|
3月前
|
开发框架 安全 搜索推荐
分享105个NET源码ASP源码,总有一款适合您
分享105个NET源码ASP源码,总有一款适合您
27 4
|
7月前
|
开发框架 前端开发 .NET
.NET 8 Release Candidate 1 (RC1)现已发布,包括许多针对ASP.NET Core的重要改进!
.NET 8 Release Candidate 1 (RC1)现已发布,包括许多针对ASP.NET Core的重要改进!
174 0
.NET 8 Release Candidate 1 (RC1)现已发布,包括许多针对ASP.NET Core的重要改进!
|
8月前
|
存储 开发框架 .NET
ASP.NET学生管理系统(.NET毕业设计)
ASP.NET学生管理系统(.NET毕业设计)
99 0
|
9月前
|
开发框架 前端开发 JavaScript
ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件
ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件
|
10月前
|
开发框架 供应链 前端开发
net基于asp.net的社区团购网站
社区团购系统依托社区团购系统和社区门店,是现在的一个重大市场和发展方向,通过研究企业在社区团购系统环境下的营销模式创新,对于普通的零售业和传统社区团购系统的转型发展具有重要的理论意义。随着互联网行业的发展,人们的生活方式发生着重大变化,人们越来越倾向于网络购物,这对传统企业来说如何把客户留下是一个重大挑战。就现在而言,由于社区团购的竞争已经进入最紧张激烈的阶段,有些团购平台甚至已经彼此之间打起了价格战,其中不乏有平台因为利润变少或资金链断裂而半途败亡。企业在实际的商业活动中,往往会面临许多等待优化的问题。因此,要在竞争激烈的市场中拔得头筹,必须重视提升对新商业模式的全面认知,科学于实际贴合的分
|
11月前
|
开发框架 .NET Apache
301重定向代码合集(iis,asp,php,asp.net,apache)
301重定向代码合集(iis,asp,php,asp.net,apache)
274 0