Effective C# 原则39:使用.Net验证

JerryXia 发表于 , 阅读 (2,632)

用户的输入可能是多种多样的:你必须在交互式的控件中尽可能的验证输入。写一些用户输入验证可能很做作,而且也有出错的可能,但还是很有必要的。不能太相信用户的输入,用户可能会输入任何内容导致异常发生,进而进行SQL注入式攻击。我们不希望任何类似这样的事情发生。你应该了解足够的信息来怀疑用户的输入。很好,每个人都应该这样做,这也就是为什么.Net框架已经扩展了这样的功能,你可以使用这些功能从而使自己的代码编写工作减到最小,因为我们要对用户输入的每一块数据都要进行验证。

.Net框架提供了不同的机制来验证用户的输入,分别可以用在Web和Windows应用程序中。Web应用程序应该在浏览器上进行数据验证,一般是使用JavaScript。一些验证控件在HTML面而中生成一些JS代码,这对你的用户来说是很有效的:在对每一项输入时,他们不用每次返回数据到服务上。这些Web控件是使用正则表达式的扩展功能来完成对用户输入的验证,这些验证可以在页面提交到服务器之间完成。即使如此,你还是要在服务器上做一些额外的验证,以免受到程序式的攻击。Windows就用程序使用不同的模式。用户的输入可以直接在应用程序中用C#代码来验证。所有的Windows控件都是可验证的,当你想通知用户的非法输入时。一般的模式是使用属性访问时的异常来指示非法的输入。UI控件捕获这些异常然后显示错误给用户。

你可以使用5个web控件来处理ASP.net应用程序中的大多数验证任务。这5个控件都是由属性来控制这些要验证的特殊的字段。RequiredFieldValidator
强制用户在给定字段中输入一个值,RangeValidator
要求特殊的字段提供的值在给定范围内,这个范围可是一个数的大小,也可以是一个字符串的长度。CompareValidator
可以让你构造一个验证规则来验证表单上两个同的控件。这三个控件都很简单。最后两个控件提供了强大的功能,可以让你根据你想要求的方法进行验证。RegularExpression
验证使用与此同时表达式来验证用户的输入。如果与比较返回匹配,输入的就是合法的。正则表达式是很有用的语言。你可以为你所有的实际情况创建正则表达式。VS.net包含了一些验证的表达式,这可以帮助你开始学习它。这有一些帮助你学习更多正则表达式的有用资料,而且我强烈鼓励你学习它。但我不能跑题而不给你提供一些最常用的构造。表5.1显示了最常用的一些正则表达式元素,你可能会在你的应用程序中用来验证输入:

表5.1 常用的正则表达式

  • [a-z] 匹配单个小写字符。括号内的字符集中的任何字符与单个字符匹配。
  • \d 任何数字。
  • ^,$ ^表示串的开始, $表示结束。
  • \w 匹配任何单词.这是[A-Za-z0-9]简写。
  • (?NamedGroup\d{4,16}) 显示两个不同的常用元素,?NamedGroup

    定义了一个特殊的变量来引用匹配。{4,16}匹配前面的构造至少4次最多16次。这一模式匹配一个至少包含4个但不超过16个数字的字符串。如果匹配存在,那么结果会存储在NamedGroup中以便后面使用。
  • (a|b|c) 匹配a或b或c。

    用坚线分开的是选择操作:输入的可是其中的任何一个。
  • (?(NamedGroup)a|b)

    可选的。这与C\#里的三元操作等效,也就是说,如果NamedGroup
    存在,匹配a,否则匹配b.
    

(译注,关于正则表达式这里只是简单的说明了一下。觉得作者在这里写正则表达式很是不伦不类,即不全也不精。)

使用这些及正则表达式的构造,你可以发现你可以验证用户提交给你的任何内容。如果正则表达式还不够,你还可以通过从CustomValidator
派生一个新在类添加你自己的验证。这是一个不小的工作,而且我尽可能的避免它。当你用C#写了一服务器函数来验证数据后,还要用ECMAscript写一个客户端的验证函数。我讨厌同样的事做两遍,而且我也尽可能的避免用ECMAscript写任何内容,所以,我喜欢粘贴正则表达式式。

例如,这有一个正则表达式,用于验证US的电话号码。它接受区号用括号括起来的,或者没有括号的,然后就是区号和号码之间的空格,交换局号(exchange
),以及号码。区号和交换局号之间的横线也是可选的:

((\(\s*\d{3}\s*\))|(\d{3}))-?\s*\d{3}\s*-\s*\d{4}

通过查验每一个组的表达式,这样的逻辑是很清楚的:

((\(\s*\d{3}\s*\))|(\d{3}))-?

这和区号匹配,它充许(XXX)或者XXX的形式,其中XXX是三个数字。任何在数字周围的空白字符是充许的。最后两个字符,-和?,是许可但不要求一个横线。

剩下的部份用于匹配电话的XXX-XXXX部份。\s匹配任意的空白,\d{3}匹配三个数字,\s*-\s*匹配一个围绕在数字边上的空白字符。最后,\d{4}精确匹配4个数字。

windows验证工作方法小有不同,你没有预先的验证分析。相反,你要写一个事件句柄到System.Windows.Forms.Control.Validating事件上,或者,如果你创建了你自己的控件,重载OnValidating方法(参见原则35)。下面是一个标准的方法:

private void textBoxName_Validating( object sender,
  System.ComponentModel.CancelEventArgs e )
{
  string error = null;
  // Perform your test
  if ( textBoxName.Text.Length == 0 )
  {
    // If the test fails, set the error string
    // and cancel the validation event.
    error = "Please enter a name";
    e.Cancel = true;
  }
  // Update the state of an error provider with
  // the correct error text. Set to null for no
  // error.
  this.errorProviderAll.SetError( textBoxName, error );
}

你有几个小工作要完成,以确保没有不合法的输入愉愉的混过去了。每一个控件包含一个CausesValidation属性,这个属性决定这个控件是否参与验证。一般情况,你应该让所有控件的这一属性为真,除非是Cancel按钮。如果你忘记了,用户还必须输出正确的值以后才能取消对话框。第二个小任务是添加OK句柄来强制验证所有的控件。验证只有在用户访问和离开控件时触发。如果用户打开了一个窗口,然后马上点OK,你的所有验证代码都不会执行。为了修正这个,你要添加OK按钮句柄,来访问所有的控件,然后强制验证它们。下面两个常规方法显示了如何正确的完成任务。递归方法处理控件以及它所包含的控件:Tab页面,控件组以及控件面板:

private void buttonOK_Click( object sender, System.EventArgs e )
{
  // Validate everyone:
  // Here, this.DialogResult will be set to
  // DialogResult.OK
  ValidateAllChildren( this );
}

private void ValidateAllChildren( Control parent )
{
  // If validation already failed, stop checking.
  if( this.DialogResult == DialogResult.None )
    return;

  // For every control
  foreach( Control c in parent.Controls )
  {
    // Give it focus
    c.Focus( );

    // Try and validate:
    if (!this.Validate( ))
    {
      // when invalid, don't let the dialog close:
      this.DialogResult = DialogResult.None;
      return;
    }
    // Validate children
    ValidateAllChildren( c );
  }
}

这些代码可以处理大多数情况。一个特殊的快捷应用就是DataGrid/DataSet的组合。在设计时指定ErrorProvider的DataSource以及DataMember属性:

ErrProvider.DataSource = myDataSet;
ErrProvider.DataMember = "Table1";

或者在运行时,调用BindToDataAndErrors 方法来同时设置:

ErrProvider.BindToDataAndErrors(  myDataSet, "Table1" );

错误会在设置DataRow.RowError 属性以及调用DataRow.SetColumnError
方法时显示特殊的错误。ErrorProvider
会在DataGrid的原始的行上的特殊单元格里显示红色的警告图标。

大概的了解(whirlwind
tour)了一下.net框架里的控件验证,这可能对你很有帮助,在很多应用程序中,你都可以创建出你所须要的高效验证。用户的输入不能完全信任:用户可能会出现错误,而且有时会有一些恶意的用户试图破坏你的应用程序。通过.Net框架已经提供的服务,你可以减少你自己的代码编写工作。验证所有用户的输入,但要使用已经提供了的高效工具。

添加新评论