ASP.NET 验证:驯服






4.63/5 (11投票s)
本文介绍如何克服 ASP.NET 验证系统施加的障碍。本文的主要重点是客户端验证控制,解决方案的核心是 JavaScript 代码。
引言
验证是每个 Web 系统开发人员迟早都会面临的必要任务之一。验证可以很简单,例如确保在字段中输入了数据(检查空字段),也可以很复杂,例如确保输入符合特定格式(与正则表达式匹配)。ASP.NET (1.1) 提供了一系列验证控件,(据说)有助于验证 Web 表单。然而,这些控件对大多数开发人员的日常需求来说并不足够;它们在许多方面都存在不足,我将在下文说明。我的代码试图填补一些空白,一些建议将指导您解决我的代码无法解决的问题。
范围
我的重点将放在提供客户端验证控制。因此,我的代码核心将是 JavaScript。C# 代码将用于方便使用 JavaScript 代码。用于扩展或重新创建现有验证控件的代码**不**在本文的范围内。
ASP.NET 验证失败
ASP.NET 提供的控件在以下方面存在不足
- 无法指定验证器相对于所单击按钮的工作时机。
- 无法将验证器分组,以便它们可以根据特定的客户端事件被激活或禁用。
- 当通过将控件的样式设置为“display: none;”来(在客户端)隐藏某个控件时,验证器也会被隐藏但仍然处于激活状态。
- 一个非常重要的缺点是,这些验证器在非 IE 浏览器中不起作用。
Firefox 中的验证
Firefox 是一个广泛使用的 Web 浏览器。它的市场份额增长到了与 Internet Explorer 相媲美的程度。当 ASP.NET 验证器被创建时,并未考虑非 IE 浏览器。这一点在它们的客户端验证在 Firefox 上不起作用(我只测试了 Firefox)这一点上显而易见。有人说生成的 JavaScript 是 IE 特定的,但我最近注意到,当浏览器是 Firefox 时,甚至没有任何 JavaScript 被生成。ASP.NET 如何为 IE 生成 JavaScript 而为 Firefox 不生成?这被称为自适应渲染,它会确定浏览器,并根据确定的浏览器以不同的方式渲染控件。
所以,我的代码之所以只在 IE 中有效,是因为验证只在 IE 中有效。我们如何处理非 IE 浏览器的情况?我们必须查看生成兼容 JavaScript 的第三方验证控件。我发现免费的 DOMValidators 非常好。我的代码可以与 DOMValidators 一起使用。
解决方案
这一切都在 JavaScript 代码中。脚本文件包含一组函数,这些函数将使开发人员能够从客户端启用/禁用验证。脚本提供了以下功能:
- 能够指定验证器何时激活何时非激活。这有助于将验证器绑定到特定的按钮。例如,如果我们(如下图所示)有两个输入部分。第一个是针对会员的,第二个是针对希望成为会员的新用户的。当会员单击登录按钮时,新用户部分的验证不应处于激活状态。因此,如果用户要登录,他们只需要提供用户名和密码,而不是新用户部分的字段。
- 能够将验证控件分组到 Group 下。这样,我们就可以根据组名禁用/启用它们。
- 能够启用/禁用特定容器内的验证器。
- 能够启用/禁用验证特定控件的验证器。
- 能够隐藏带有其内部所有验证器的特定容器(禁用它们,而不仅仅是隐藏它们)。
- 无需构建新控件甚至扩展现有控件即可实现所有这些控制。
JavaScript
JavaScript 函数分为三组:
- 控制特定容器内的验证器。如果未指定容器,则会影响页面中的所有验证器(`enableValidators`、`disableValidators`)。
- 控制验证特定控件 X 并位于控件 Y 内的验证器。如果未指定 Y,则会影响页面中所有验证 X 的控件(`enableCTVValidators`、`disableCTVValidators`)。
- 控制属于特定组的验证器(`enableGroupValidators`、`disableGroupValidators`)。
还有一组用于显示/隐藏页面特定部分,同时禁用和隐藏其中所有包含的验证器(`show`、`hide`、`show/hide`)。此组使用上面三组中的函数。
使用方法
可以通过 JavaScript 调用上述函数来使用它们。这些调用通常位于我们页面中操作控件的客户端事件处理程序中。例如,我们可以在 **Login** 按钮的 `onclick` 事件中调用 `enableValidators()`。
btnLogin.Attributes["onclick"] =
"enableValidators('" + tblMember.ClientID + "');"+
" disableValidators('" + tblNew.ClientID + "');";
上面的代码将使 `btnLogin` 按钮启用 `tblMember` 表中的所有验证器,并禁用 `tblNew` 表中的所有验证器。
我们还可以通过为某些验证器指定 `groupID` 来将它们分组。这应该在我们页面的 HTML 视图中完成。
<asp:requiredfieldvalidator id="rfvLoginName"
ErrorMessage="Username Required" Display="Dynamic"
ControlToValidate="txtLoginName" groupID="Member" runat="server">
!</asp:requiredfieldvalidator>
代码隐藏中是这样的:
btnLogin.Attributes["onclick"] =
"enableGroupValidators('Member'); disableGroupValidators('NewMember');";
当单击 `btnLogin` 按钮(在客户端单击)时,属于名为 `Member` 的组的验证器将被启用,属于名为 `NewMember` 的组的验证器将被禁用。当单击代码中的 `btnSubmit` 按钮时,会发生相反的行为。
btnSubmit.Attributes["onclick"] = "enableGroupValidators('NewMember');" +
" disableGroupValidators('Member');";
当任何按钮失去焦点时,整个页面的验证都会重新启用。这是一个合适的解决方案,因为 `onblur` 事件发生在按钮单击**之后**。
有关使用这些函数和其他函数的更多示例,请参阅本文附带的演示项目。
Server Side Validation
尽管这不是本文的重点,但让我们的页面也在服务器端进行验证至关重要。这是因为客户端验证很容易被绕过。
由于我们在客户端启用/禁用验证器,并且服务器端无法感知我们在客户端所做的更改(即使我们在客户端禁用它们,验证器在服务器端仍然有效),因此检查 `Page.IsValid` 属性将无效。这就是我们使用此方法的原因:
private bool checkIsValid(Control c)
{
bool result = true;
foreach( Control child in c.Controls)
{
if(child is BaseValidator && !((IValidator)child).IsValid)
{
return false;
}
result = result & checkIsValid(child);
}
return result;
}
上述方法检查给定控件内的所有验证器。如果所有验证器都有效,则提供的控件有效;否则,整个控件无效。
JavaScript 详细说明
对于那些只想使用代码的人来说,这**不是必读**。本节内容是为那些对了解代码的 JavaScript 内部工作原理感兴趣的人准备的。
每种类型的函数都有两个版本,一个用于启用验证,一个用于禁用验证。我们从顶部开始。在“包含的验证器”部分,有一个主要的遍历函数,用于查找目标验证控件并设置它们的***状态。
function traverseTree(status, control)
{
if(control == null)
{
for(var i = 0; i < Page_Validators.length; i++)
{
Page_Validators[i].enabled = status;
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
else
{
//this is a way to check that the control is a validation control
if(control.controltovalidate != null)
{
control.enabled = status;
control.style.display = status ? 'inline' : 'none';
}
for( var i=0; i < control.childNodes.length; i++)
{
traverseTree(status, control.childNodes[i]);
}
}
}
此函数的作用是:如果控件为 null
(换句话说,如果提供了容器控件),则设置**所有**验证器状态的最简单方法是循环遍历 `Page_Validators` 数组。每当页面中使用验证时,都会生成此数组。要设置验证器的状态,我们会将其 `enabled` 属性设置为状态(即 true
或 false
),并通过将其 `style.display` 设置为 `none` 或 `inline` 来隐藏/显示验证器。现在,如果提供了容器(控件 != null
),那么我们将递归遍历控件树,沿途设置所有验证器的状态。我们如何知道某个控件是验证控件?通过检查其 `evaluationfunction` 属性。
现在,这是我们在容器内启用验证器的方法:
function enableValidators(containerID)
{
var control;
if(containerID == null)
{
control = null;
}
else
{
control = document.getElementById(containerID);
}
if( containerID == null || control != null )
traverseTree(true, control);
}
此函数检查是否提供了 `containerID`;如果提供了,则使用 `getElementById` 找到的相应控件调用 `traversTree` 函数。`disableValidators` 函数类似。
现在,我们可以在这里看到 `ControlToValidate` 函数的遍历函数:
function traverseTreeCTV(status, controlToValidateID, control)
{
if(control == null)
{
for(var i = 0; i < Page_Validators.length; i++)
{
if(Page_Validators[i].controltovalidate != null &&
Page_Validators[i].controltovalidate == controlToValidateID)
{
Page_Validators[i].enabled = status;//disable validator
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
}
else
{
if(control.controltovalidate != null &&
control.controltovalidate == controlToValidateID)
{
control.enabled = status;//disable validator
control.style.display = status ? 'inline' : 'none';
}
for( var i=0; i < control.childNodes.length; i++)
{
traverseTreeCTV(status, controlToValidateID, control.childNodes[i]);
}
}
}
如果未提供容器控件,则会设置页面中所有验证 `controlToValidateID` 控件的状态。和以前一样,我们循环遍历 `Page_Validators` 数组。如果提供了容器,那么我们将递归遍历控件树,设置所有具有 `controltovalidate` 为 `controlToValidateID` 的控件的状态。
关于 `enableCTVValidators` 和 `disableCTVValidators` 没有什么新内容,它们只是调用上述函数。
现在,关于组验证器函数:
function setGroupValidatorsStatus(groupID, status)
{
for(var i = 0; i < Page_Validators.length; i++)
{
if(Page_Validators[i].attributes['groupID'].value == groupID)
{
Page_Validators[i].enabled = status;
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
}
前面所有函数的作用是查找所有具有 `groupID` 属性且值为 `groupID` 的验证器。找到这些控件后,将相应地设置它们的状态。
同样,`enableGroupValidators` 和 `disableGroupValidators` 仅仅调用上述函数。
我将把显示/隐藏函数留给您自己去研究。
关于演示项目
演示项目提供了使用大多数提供函数的示例。在该项目中,有一个名为“`WebForm1`”的 Web 窗体,它导入了 JavaScript 文件“*ValidationDefeater.js*”。
在“`WebForm1`”中,有一组 `TextBox` 及其相应的验证器以及用于提交表单的按钮。这些分为两个部分:一个用于会员,另一个用于新用户。逻辑上,这两个部分是分开的,但功能上,它们的验证器并非如此,至少在没有我们的方法的情况下。通过使用描述的技术,它们真正变成了两个独立的部分。
有一个名为 `ddlMode` 的下拉列表,用于选择强制执行我们分离的模式。模式有:`Group` 模式、`Container` 模式、`ControlToValidate` 模式和 `Show` / `Hide` 模式。
当选择 `ControlToValidate` 模式时,会出现两个新的下拉列表。每个下拉列表属于两个部分之一,并且每个下拉列表都包含相应部分中所有 `TextBox` 的名称。要点是选择我们希望禁用验证的控件的名称。
每个部分所属的两个 `btnLogin` 和 `btnSubmit` 按钮都在代码隐藏中进行了准备。这些按钮的客户端 `onclick` 事件根据所选模式设置为适当的函数调用。
当选择 `Show` / `Hide` 模式时,会出现两个 `RadioButton`。选择这两个中的一个将显示一个部分(禁用其中的所有验证)并显示另一个(启用其中的所有验证)。
至此,一切都讲完了。
最后
我希望我提供的内容能帮助到大多数读者。请随时评论并提供有助于改进的建议或替代方案。如果您欣赏本文,请通过投票来表示。谢谢。
更新
- 2006 年 5 月 17 日
- 对演示代码进行了一些修改。
- 现在使用 `evaluationfunction` 而不是 `controltovalidate` 来检查验证器。
- 现在,在单击按钮后(`onblur`)重新启用验证。
- 添加了“服务器端验证”部分。