65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 验证:驯服

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (11投票s)

2006年5月13日

CPOL

9分钟阅读

viewsIcon

83562

downloadIcon

240

本文介绍如何克服 ASP.NET 验证系统施加的障碍。本文的主要重点是客户端验证控制,解决方案的核心是 JavaScript 代码。

Sample Image - ASPNET_Validation_Tamed.jpg

引言

验证是每个 Web 系统开发人员迟早都会面临的必要任务之一。验证可以很简单,例如确保在字段中输入了数据(检查空字段),也可以很复杂,例如确保输入符合特定格式(与正则表达式匹配)。ASP.NET (1.1) 提供了一系列验证控件,(据说)有助于验证 Web 表单。然而,这些控件对大多数开发人员的日常需求来说并不足够;它们在许多方面都存在不足,我将在下文说明。我的代码试图填补一些空白,一些建议将指导您解决我的代码无法解决的问题。

范围

我的重点将放在提供客户端验证控制。因此,我的代码核心将是 JavaScript。C# 代码将用于方便使用 JavaScript 代码。用于扩展或重新创建现有验证控件的代码**不**在本文的范围内。

ASP.NET 验证失败

ASP.NET 提供的控件在以下方面存在不足

  1. 无法指定验证器相对于所单击按钮的工作时机。
  2. 无法将验证器分组,以便它们可以根据特定的客户端事件被激活或禁用。
  3. 当通过将控件的样式设置为“display: none;”来(在客户端)隐藏某个控件时,验证器也会被隐藏但仍然处于激活状态。
  4. 一个非常重要的缺点是,这些验证器在非 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 代码中。脚本文件包含一组函数,这些函数将使开发人员能够从客户端启用/禁用验证。脚本提供了以下功能:

  1. 能够指定验证器何时激活何时非激活。这有助于将验证器绑定到特定的按钮。例如,如果我们(如下图所示)有两个输入部分。第一个是针对会员的,第二个是针对希望成为会员的新用户的。当会员单击登录按钮时,新用户部分的验证不应处于激活状态。因此,如果用户要登录,他们只需要提供用户名和密码,而不是新用户部分的字段。
  2. 能够将验证控件分组到 Group 下。这样,我们就可以根据组名禁用/启用它们。
  3. 能够启用/禁用特定容器内的验证器。
  4. 能够启用/禁用验证特定控件的验证器。
  5. 能够隐藏带有其内部所有验证器的特定容器(禁用它们,而不仅仅是隐藏它们)。
  6. 无需构建新控件甚至扩展现有控件即可实现所有这些控制。

JavaScript

JavaScript 函数分为三组:

  1. 控制特定容器内的验证器。如果未指定容器,则会影响页面中的所有验证器(`enableValidators`、`disableValidators`)。
  2. 控制验证特定控件 X 并位于控件 Y 内的验证器。如果未指定 Y,则会影响页面中所有验证 X 的控件(`enableCTVValidators`、`disableCTVValidators`)。
  3. 控制属于特定组的验证器(`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` 属性设置为状态(即 truefalse),并通过将其 `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`)重新启用验证。
    • 添加了“服务器端验证”部分。
© . All rights reserved.