增强标准验证器输出的呈现






4.68/5 (23投票s)
2006 年 5 月 21 日
8分钟阅读

134141

759
ASP.NET 验证器的常规功能有多灵活?在本文中,我将展示如何在服务器或客户端出错时自定义验证器附加控件的外观,甚至在不进行回发的情况下调用自定义客户端函数。
描述问题
在我正在进行的一个项目中,我遇到了一个问题。客户要求更改出现的错误的默认显示方式。他希望在用户在表单上输入错误数据时,使用自定义颜色突出显示字段的背景。显示验证过程失败的普通技术是在某个位置显示一些红色文本,或者只是一个星号。此外,我们还可以放置一个 ValidationSummary
控件来显示汇总的输出。我一直在搜索 .NET 中是否有内置功能可以在回发之前运行自定义客户端函数,但没有成功。
在本文中,我将展示如何在服务器端和客户端出错时为附加控件添加特殊的级联样式表类名,甚至调用自定义客户端函数。此外,我还将展示如何自定义验证摘要结果。
您可以 下载源代码(10.9 Kb),并轻松地将功能添加到您的表单中。有一个名为 HighlightValidators
的类库。它包含所有类型的增强型验证器,如 RequiredValidator
、CompareValidator
、RangeValidator
、RegularExpressionValidator
和 CustomValidator
。在 演示应用程序(6.52 Kb) 中,您还可以找到一个示例表单。
如何使用解决方案
要使用它,您需要执行以下步骤:
- 将 UserControls.HighlightValidators.dll 从 HighlightValidators/bin/Release 文件夹复制到您 Web 应用程序的 bin 文件夹。
- 将以下块添加到 web.config 文件中
... <pages> <controls> <add tagPrefix="uc" assembly="UserControls.HighlightValidators" namespace="UserControls.HighlightValidators" /> </controls> </pages> ...
这将为整个 Web 应用程序注册 UserControls.HighlightValidators 程序集中的 Highlight 控件。
- 之后,您可以声明
HighlightRequiredValidator
,例如,如下所示:<uc:HighlightRequiredValidator id=HighlightRequiredValidator1 ErrorCssClass="on_error" ErrorMessage="First Name is required" ControlToValidate="TextBox1" runat="Server" />
我将
ErrorCssClass
设置为“on_error
”。现在,当验证失败时,TextBox1
控件将根据on_error
类进行自定义。 - 所有 Highlight 验证器还具有
ErrorClientFunction
属性。在出错时,您可以使用它来调用自定义客户端函数。<uc:HighlightCompareValidator ID="HighlightCompareValidator1" runat="server" ControlToValidate="TextBox1" ErrorMessage="Number of books must be numeric" ErrorCssClass="on_error" ErrorClientFunction="onError" Operator="DataTypeCheck" Type="Integer" />
现在,您需要在页面上声明
onError()
函数:function onError(val) { alert("Number of books must be numeric"); }
请注意,此函数接受一个参数
val
。此对象描述了验证失败的验证器。有关更多信息,请参阅下面的部分。
深入代码
为了增强标准的验证器,我将它们用作继承类,并创建了一个以“Highlight”为前缀的新验证器分支,因为我只想添加一些期望的功能并保持兼容性,这样开发人员就可以轻松地在标准的 ASP.NET 验证器和增强的 Highlight 验证器之间切换。
首先,让我们看看更改类名是如何工作的。代码分为两部分:服务器端和客户端。服务器端一切都很简单:我只添加了一个检查,看验证器是否有效,或者验证过程是否失败。如果无效,我会将错误类名添加到附加控件中。
protected override void OnPreRender(EventArgs e)
{
WebControl control = (WebControl)Parent.FindControl(ControlToValidate);
ClassNameHelper.Remove(control, this.errorCssClass);
if (!IsValid)
{
ClassNameHelper.Add(control, this.errorCssClass);
}
base.OnPreRender(e);
}
ClassNameHelper.Remove()
方法在任何情况下都会删除错误类名,因为之前的回发可能验证失败并且类名已被添加。现在,我们需要先删除它,然后再次检查。如果 IsValid
再次等于 false
,那么我们需要使用 ClassNameHelper.Add()
方法再次添加错误类名。
到目前为止没有什么有趣的。但是,让我们来看看如何在用户回发表单之前在客户端更改附加控件的类名。正如我在引言部分所说,如果我们设置 EnableClientScript
为 true
(这是默认值),那么在用户输入错误数据的情况下,验证客户端脚本不会将表单回发到服务器并添加一些红色文本错误描述或显示验证摘要。为了研究这个过程,我进入了 aspnet_client 文件夹,并查看了 WebUIValidation.js 脚本文件。
我在那里找到了 ValidatorUpdateDisplay
函数。
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;
}
}
val.style.visibility = val.isvalid ? "hidden" : "visible";
}
就是这样!客户端脚本使用它来显示/隐藏验证器的红色错误消息。因此,如果我们能够覆盖它,我们就可以添加一些额外的代码,这将有助于实现增强的功能。为了实现这一点,我使用了一个小技巧——劫持。例如,如果我们有一个函数 test
在某个包含的 JavaScript 文件中,并且我们需要在某个 HTML 文件中覆盖它,我们可以这样做:
var oldTest;
function newTest()
{
// call oldTest function
oldTest();
// execute some additional code
alert("we've overrided it");
}
window.onload = function()
{
// hijack test() method
oldTest = test;
test = newTest;
}
当页面加载时(window.onload
事件),我们将 test()
函数指针保存在 oldTest
变量中,并将 test()
的当前指针替换为 newTest()
指针。所以现在,如果我们调用 test()
函数,实际上我们正在调用 oldTest()
函数。请注意,如果您在没有括号的情况下使用 JavaScript 函数的名称,它只是一个指针,但如果您需要调用函数,则需要在末尾使用“()”。
让我们回到 ValidatorUpdateDisplay()
函数。现在我们知道如何覆盖它。我们还拥有作为参数的 val
对象。此对象描述了正在进行验证过程并在此后更改状态的验证器。验证器对象的一些重要属性在下表中有所描述:
ID |
等于验证器的 ClientID 属性 |
controltovalidate |
包含附加到验证器的控件的 ClientID |
isvalid |
指示验证过程是否失败 |
errormessage |
包含 ErrorMessage 属性 |
现在,我将覆盖 ValidatorUpdateDisplay()
函数。我需要遍历当前表单上所有 Highlight 验证器的列表,并在验证失败时(isvalid == false
)向控件添加自定义错误类名。我使用 Page_HighlightValidators
数组来保存有关 Highlight 验证器的所有必要信息(验证器 ClientID
、ErrorCssName
等),以便客户端脚本可以访问。这发生在 Highlight 验证器的 OnLoad
事件期间。
private string errorCssClass;
public string ErrorCssClass
{
get { return this.errorCssClass; }
set { this.errorCssClass = value; }
}
private string errorClientFunction;
public string ErrorClientFunction
{
get { return this.errorClientFunction; }
set { this.errorClientFunction = value; }
}
protected override void OnLoad(EventArgs e)
{
ClientScriptManager cs = Page.ClientScript;
...
Control control = Parent.FindControl(this.ControlToValidate);
if (control != null)
{
if (!cs.IsClientScriptBlockRegistered(this.GetType(),
String.Format("HighlightValidation_{0}", this.ClientID)))
{
cs.RegisterClientScriptBlock(this.GetType(),
String.Format("HighlightValidation_{0}", this.ClientID),
String.Format("Page_HighlightValidators.push(
new Array('{0}', '{1}', '{2}', '{3}'));",
this.ClientID, control.ClientID, this.errorCssClass,
this.errorClientFunction), true);
}
}
...
}
我使用 Array
对象的 push()
方法将新项添加到 Page_HighlightValidators
数组,因为我实际上不知道表单上 Highlight 验证器的放置顺序。ErrorCssClass
和 ErrorClientFunction
是 Highlight 验证器的新属性。我们可以使用它们来设置在验证失败时需要运行的自定义错误类名和客户端函数名。
最终,我可以在已经覆盖的 ValidatorUpdateDisplay()
函数中使用循环,并在验证失败时添加自定义错误类名。
function newValidatorUpdateDisplay(val)
{
// call hijacked ValidatorUpdateDisplay() method
oldValidatorUpdateDisplay(val);
for (i = 0; i < Page_HighlightValidators.length; i++)
{
if (val.id == Page_HighlightValidators[i][0] &&
val.controltovalidate == Page_HighlightValidators[i][1])
{
var control = document.getElementById(Page_HighlightValidators[i][1]);
var errorClassName = Page_HighlightValidators[i][2];
var errorCallFunction = Page_HighlightValidators[i][3];
// add class and change tooltip
if (errorClassName != "")
{
RemoveClassName(control, errorClassName);
if (!val.isvalid)
{
AddClassName(control, errorClassName);
control.title = val.attributes.title.value;
}
}
// call custom function
if (errorCallFunction != "" &&
eval("typeof("+errorCallFunction+")") == "function")
{
if (!val.isvalid)
{
eval(errorCallFunction+"(val)");
}
}
}
}
}
我使用 RemoveClassName
和 AddClassName
函数,而不是简单地用错误类名替换类名。这些函数能够添加和删除类名。它们能够附加几个类名,用空格分隔它们,如下所示:
<div class="class_one class_two">...</div>
另请注意,class_two
类中的属性会覆盖 class_one
类中的属性。因此,我们只更改指示错误的属性,而不破坏其他属性。
在上面的代码中,我还添加了调用自定义错误客户端函数,并将 val
对象作为参数传递给它。
客户端自定义验证摘要
现在,我们增强了验证器,并且可以通过自定义错误类名和自定义客户端函数以我们喜欢的任何方式向使用我们表单的客户指示发生的错误类型。但是,让我们看看 ValidationSummary
控件。它的默认功能如何能够对我们变得灵活?
例如,我想将 ValidationSummary
控件放置为位于浏览器窗口中心的 div
并自定义其外观。现在,让我们暂时忘记 asp:ValidationSummary
控件。让我们再次回到 aspnet_client 文件夹,并打开 WebUIValidation.js 文件。有一个 ValidationSummaryOnSubmit()
函数,ValidationSummary
控件在需要显示错误列表时会调用它。很容易发现此函数使用一个全局数组 Page_Validators
,其中包含当前表单上所有验证器的集体信息。好了,这就是我们需要的。我们可以使用此数组遍历所有验证器并检查它们的状 态。
接下来,我将为我的自定义 Validation Summary 控件创建一个 div
:
<div id="SummaryValidation">
<span>
<p style="font-weight:bold">The following errors were occured:</p>
<ul id="ValidationProblems"></ul>
</span>
</div>
我已经添加了一个标题和一个列表标签,我计划在其中显示所有错误。为了在浏览器窗口中居中 div
,我使用了一个小技巧。有关更多详细信息,请参阅这篇出色的文章:Horizontally Centered Absolute Positioning。
在下一步中,我们需要将 JavaScript 函数附加到表单的 Submit 事件,在那里我们将检查所有验证器并创建错误列表(如果存在);否则,我们将只允许表单回发到服务器。
protected void Page_Load(object sender, EventArgs e)
{
ClientScriptManager cs = Page.ClientScript;
if (!cs.IsOnSubmitStatementRegistered(this.GetType(),
"PrepareValidationSummary"))
{
cs.RegisterOnSubmitStatement(this.GetType(),
"PrepareValidationSummary", "CheckForm()");
}
}
现在,我声明 CheckForm()
函数如下:
function CheckForm()
{
if (!Page_IsValid)
{
var s = "";
for (i=0; i<Page_Validators.length; i++) {
if (!Page_Validators[i].isvalid &&
typeof(Page_Validators[i].errormessage) == "string") {
s += "<li>" + Page_Validators[i].errormessage + "</li>";
}
}
if (s != "")
{
document.getElementById('ValidationProblems').innerHTML = s;
document.getElementById('SummaryValidation').style.display = 'inline';
}
}
}
在 CheckForm()
中,我检查所有验证器的有效性并收集错误信息。然后,如果至少存在一个错误,我将显示验证摘要。
为了隐藏验证摘要,创建 HideValidationSummary()
函数,并将其附加到 document.onlick
事件,这样当客户单击浏览器窗口中的某处时它就会消失。
我希望这对您有所帮助,如果您留下您的意见、更正或任何其他想法,我将不胜感激。感谢您的时间。
更改历史
- 2006/7/7 - 重构:添加了
EventHelper
类,以共享在每个 Highlight 验证器声明中存在的某些重复代码块。