惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
H
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog RSS Feed
Jina AI
Jina AI
Stack Overflow Blog
Stack Overflow Blog
量子位
博客园_首页
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Forbes - Security
Forbes - Security
IT之家
IT之家
N
News and Events Feed by Topic
S
Security Affairs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Webroot Blog
Webroot Blog
Recorded Future
Recorded Future
L
LangChain Blog
Y
Y Combinator Blog
AI
AI
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Know Your Adversary
Know Your Adversary
AWS News Blog
AWS News Blog
Help Net Security
Help Net Security
Cyberwarzone
Cyberwarzone
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google Online Security Blog
Google Online Security Blog
V2EX - 技术
V2EX - 技术
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
PCI Perspectives
PCI Perspectives
I
Intezer
T
Tenable Blog
G
Google Developers Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Troy Hunt's Blog
L
LINUX DO - 最新话题
云风的 BLOG
云风的 BLOG
C
CXSECURITY Database RSS Feed - CXSecurity.com
有赞技术团队
有赞技术团队
O
OpenAI News
P
Proofpoint News Feed
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Last Week in AI
Last Week in AI
S
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
Blog — PlanetScale
Blog — PlanetScale

博客园 - 灵魂边缘

Detecting the File Download Dialog In the Browser The IE 'non-disappearing content' bug 10个强大的Javascript表单验证插件推荐 使用javascript检测客户端是否是mobile的类型,如果是就跳转到另外一个页面 在javascript中对一个对象数组进行排序 Devenv /ResetSkipPkgs Invalid Viewstate error and how to fix it? 在asp.net中如何管理cache 用Javascript取得URL参数(Get URL parameters/query string using Javascript) - 灵魂边缘 用JavaScript对表格排序 使用 jQuery 简化 Ajax 开发 Localization/Globalization in ASP.Net 2.0 Generating change scripts in SQL Server 2005 Management Studio Redirecting to custom 401 page when "Access denied" occures within an ASP.NET application - 灵魂边缘 .net中完成海量数据的批量插入和更新 - 灵魂边缘 - 博客园 存储过程跨系统跨数据库操作 ASP.NET 2.0 中配合Master Page 使用CSS - 灵魂边缘 正则表达式全部符号解释 - 灵魂边缘 - 博客园 [转]asp.net导出数据到Excel的三种方法
ASP.NET:创建Linked ValidationSummary, 深入理解ASP.NET的Validation (转)
灵魂边缘 · 2009-10-12 · via 博客园 - 灵魂边缘

一 、实现的效果

我想对于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。