商务应用程序中的常见验证规则

[原文发表地址]   Common Validation Rules in LightSwitch Business Applications

[原文发表时间] 2011-11-11 7:45 AM

对于任何与人进行交互的应用程序(及其他系统)来说,尤其是商务应用程序,检查数据输入的有效性是一个常见的需求。我从来没有看到或写过一个数据输入的应用程序,在输入数据时没有使用常见的验证规则。LightSwitch通过数据设计器以及对自定义代码的支持可以用很多方式来执行声明的验证。字段(或属性)的验证仅仅只是LightSwitch 中编写商务规则的一个方面,但它绝对是重要的一个。它是让你保持用户输入的数据一致的"第一道防线"。

虽然LightSwitch 有一些内置的商业类型和声明性的验证设置,但很多时候我们需要编写一小段代码来查看界面上输入数据的格式。在美国常用到的是州、邮政编码、身份证号、UPIN码、ISBN等等。但是在人名或地方名或其他基于字符串操作规则的地方,您可能想要防止像数字型和符号的值。

在本篇博客中,我将为你们展示在LightSwitch中如何定义声明性的规则以及编写自定义的验证代码,我也会展示一些验证字符串时常用的样式。让我们开始吧!

声明性的验证规则

首先,我们来说说在无需编写任何代码的条件下,您可以声明哪些特定类型的验证。让我们来看一个用数据设计器设计的客户实体,它包含了以下字段:

image

必需的字段 & 商务类型

验证这些字段的第一步是确定它们的类型,以及哪些是必需的。LightSwitch将自动处理必需的字段和商务类型的验证。因此,您可以通过数据设计器以声明方式设置它们,而无需编写任何代码。内置的商务类型有电子邮件、电话、钱和图片,但这是可扩展的,所以您可以下载更多。在这个客户实体中,您可以注意到,我只要求姓氏是必填的。必需的字段在界面上以粗体显示,当它是空白时,它会阻止用户保存数据。

image

任何验证失败时,当用户把光标停在字段中或点击界面顶部的验证概要时,消息将会显示出来

image

请注意,在客户实体中,我为电话和电子邮件字段也选取了“电话号码”和“电子邮件”商务类型。商务类型是内置的验证,在属性窗口中您可以为它设置额外的属性来指示如何验证。对于电子邮件商务类型,您可以选择是否提供默认的电子邮件域以及是否必需域。

image

对于电话号码商务类型,您可以提供额外的验证格式以及以何种顺序验证:

image

指定字段的最大长度

另一个重要的声明性的验证规则是指定最大长度。默认情况下,所有字符串字段的最大长度为 255 个字符。这适用于大多数字段,因为它们有可变长度,而且对于我们的数据来说,255个字符有足够的空间了。然而SSN、州和邮政编码字段分别被限定为11、2、和10个字符的长度。任何超过这个长度的都不是有效数据。您可以在属性窗口的验证部分中指定最大长度。

image

使用唯一索引防止重复

您还可以在唯一索引中包含必需字段来防止重复。例如,如果我想在系统中防止相同的姓氏,我可以把它包含在唯一索引中。

image

说明:在表中将字段添加到唯一索引中是由数据库强制执行的。因此在数据被保存和在服务器端检查后,验证消息将会出现。在我的客户实体例子中,以这种方式限制重复并不是一个好的想法,客户拥有相同的姓氏是很常见的。您可能会考虑使用SSN,但是这样我们就不得不将它设置成为必需字段,只有拥有SSN的客户才可以进入系统。在这种情况下,限制性太强了,但是如果您想要阻止重复记录,唯一索引中包含的字段也可以为其他类型的数据实体工作

自定义验证规则

当您不能以声明的方式表达验证规则时,您需要编写一些代码。若要编写自定义验证代码,选择所需实体的属性,然后从下拉菜单中选择“编写代码”按钮,选择property_ Validate方法:

image

这将会为方法打开代码编辑器,以编写自定义验证代码。当一个规则失败时,您可以使用results对象来添加错误消息:

    1: Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
    2:  ' Check the rule, if it fails then display an error message on the field:
    3:  results.AddPropertyError("<Error Message>")
    4: End Sub
 您也可以指定警告信息和通知信息,只有错误消息才能阻止数据被保存。想要指定某个字段上的警告信息或通知信息,使用AddPropertyResult
 方法, 并将严重性的级别传递给它:

image

你还可以指定实体级别的错误,而不是特定于某些字段。相应的,它们将只会出现在界面顶部的验证概要中。为此,你可以使用AddEntityError和AddEntityResult方法。 这些属性验证方法先在客户端上运行,然后再在服务器端运行,但是,你只需要编写一次,LightSiwtch会在适当的时候调用它们的。想要更深了解验证框架,请阅读:LightSwitch 应用程序中的数据验证概述

简单的字符串验证和格式设置

我想要执行的第一个规则是在州字段里始终应以大写字母输入。有很多的字符串方法可用于操纵和验证字符串数据。例如,对于格式字符串,您可以使用String.Format()方法。也还有很多方法:可以在某个位置查找字符(IndexOf),可以查看是否一个字符串包含另一个字符串(Contains),返回字符串的一部分(Substring),修剪空白(Trim)。更多请查看关于你可以使用的所有字符串方法的文档

为了设置州字段的格式,我们可以简单地使用ToUpper() 方法。因为州不是必需的字段,我们先查看州的属性是否有值以及是否大写。

    1: Private Sub State_Validate(results As EntityValidationResultsBuilder)
    2:  If Me.State <> "" Then
    3:      Me.State = Me.State.ToUpper
    4:  End If
    5: End Sub
 请注意:这不会向用户报告验证错误,它只是设置输入的字符串的格式。您也可以使用property_Validate方法来执行格式设置,因为当方法在客户端
 运行,用户退出该字段时,_Validate方法会被触发。然而,我们仍然需要验证是否输入了一个有效的州代码-——“AA”目前是被允许的,但它不是
 美国的一个有效的州。为了验证所有可能的州代码组合,我们可以使用使用正则表达式。

使用正则表达式

正则表达式用于文本编辑器、 实用程序和编程语言来搜索和处理基于样式的文本。正则表达式也用于 web来验证用户的输入。事实上,我最喜欢的网站之一——www.RegExLib.com上有千种社区提交的样式,您可以在自己的验证规则中使用它们。为了在_Validate方法中使用正则表达式,您需要使用位于System.Text.RegularExpressions namespave下的RegEx类。因此,为了检查州是美国一个有效的州,我们可以编写以下以粗体显示的代码:

 Imports System.Text.RegularExpressions
  
 Namespace LightSwitchApplication
     Public Class Customer
  
         Private Sub State_Validate(results As EntityValidationResultsBuilder)
             If Me.State <> "" Then
                 Me.State = Me.State.ToUpper
  
                 Dim pattern =   "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|"   + 
  "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|"   + 
  "T[NX]|UT|V[AIT]|W[AIVY]))$" 
                 <br>                 If Not Regex .IsMatch( Me .State, pattern)  Then
 results.AddPropertyError(  "Please enter a valid US State."  ) 
 End If
             End If
         End Sub
     End Class
 End Namespace

同样,我们也可以使用正则表达式检查邮政编码。不过我想允许5位和9位的邮政编码。同时我想允许用户不指定破折号,因此我们也先要做一些格式设置。

 Private Sub ZIP_Validate(results As EntityValidationResultsBuilder)
     If Me.ZIP <> "" Then
         'Add the dash if the user didn't enter it and the ZIP code is 9 characters
         If Not Me.ZIP.Contains("-") AndAlso Me.ZIP.Length = 9 Then
             Me.ZIP = Me.ZIP.Substring(0, 5) + "-" + Me.ZIP.Substring(5)
         End If
         'Now validate based on regular expression pattern
         If Not Regex.IsMatch(Me.ZIP, "^\d{5}$|^\d{5}-\d{4}$") Then
              results.AddPropertyError("Please enter a valid US ZIP code.")
         End If
     End If
 End Sub

我想要执行的另一个规则是SSN 格式,它的格式是“3位(破折号)2位(破折号)4位(破折号)”。我想做与我们上面做过的相同类型的事情,我们不要求用户输入破折号。所以我们可以编写下面的代码:

 Private Sub SSN_Validate(results As EntityValidationResultsBuilder)
     If Me.SSN <> "" Then
         'Add the dashes if the user didn't enter it and the SSN is 9 characters
         If Not Me.SSN.Contains("-") AndAlso Me.SSN.Length = 9 Then
             Me.SSN = Me.SSN.Substring(0, 3) + "-" + Me.SSN.Substring(3, 2) + "-" + Me.SSN.Substring(5)
         End If
  
         'Now validate based on regular expression pattern
         If Not Regex.IsMatch(Me.SSN, "^\d{3}-\d{2}-\d{4}$") Then
             results.AddPropertyError("Please enter a valid SSN (i.e. 123-45-6789).")
         End If
     End If
 End Sub

你可以用正则表达式和字符串操作做很多事情,我想要执行的最后一条规则是不允许用户在姓氏和名字字段中输入数字或符号。它们应只包含字母字符和允许使用空格。我们可以做像这样的事情来执行:

 Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
     If Me.LastName <> "" Then
         'This pattern only allows letters and spaces
         If Not Regex.IsMatch(Me.LastName, "^[a-zA-Z\s]+$") Then
             results.AddPropertyError("Last Name can only contain alphabetical characters.")
         End If
     End If
 End Sub
  
 Private Sub FirstName_Validate(results As EntityValidationResultsBuilder)
     If Me.FirstName <> "" Then
         'This pattern only allows letters and spaces
         If Not Regex.IsMatch(Me.LastName, "^[a-zA-Z\s]+$") Then
             results.AddPropertyError("First Name can only contain alphabetical characters.")
         End If
     End If
 End Sub

请注意:在这个最后的例子中,我使用的是相同的格式。您可能会让字段在应用程序中跨实体,那样可以使用相同的验证查看了。当您开始复制代码时,您应该停下来,然后考虑将它整合到单个库或类中,那样您就可以调用了。这样的话,如果您的验证代码中存在缺陷,您只需在一个地方修复。记住,代码写的越少,缺陷也会越少;-)

创建一个通用的验证模块

让我们创建一个验证模块,那样我们可以从我们的验证方法中调用,这些验证方法封装了我们想要支持的应用程序中所有实体的所有类型的验证例程。在解决方案资源管理器中转换到“查看文件”,展开Common 项目下的UserCode文件夹,右击它,添加一个新的模块,我给它命名为CommonValidation.

image

为了更容易找到和从_Validate方法中调用这些验证规则,我将它们创建为Extension 方法。基本上扩展方法"扩展"类型有,如字符串、 整数或你自己的自定义方法的任何其他对象类型。最酷的是在你键入 “.”后,它们将出现在IntelliSense的形式。键入后,要替代模块中的extension方法,我们只需导入System.Runtime.CompilerServices,然后将<Extension()>的属性传给我们的方法。

在我们添加复杂的验证代码之前,让我们看一个简单的例子。例如,让我们创建一个称为"MyMethod"扩展字符串类型的extention方法。您的extention方法中的第一个参数是要扩展的类型。这就是我们如何编写模块。(注意:在<Extension>上面我通过键入三个单引号 ('‘ ') 来添加注释部分):

 Imports System.Runtime.CompilerServices
  
 Module CommonValidation
     ''' <summary>
     ''' This is my extension method that does nothing at the moment. ;-)
     ''' </summary>
     ''' <param name="value">extend the string type</param>
     ''' <remarks></remarks>
     <Extension()>
     Public Sub MyMethod(ByVal value As String)
         'Do something
     End Sub
 End Module

如果我们回到property_Validate方法中的某个方法,然后我们在IntelliSense上的任何字符串类型上将看到此扩展方法。(你需要转到"全部(All)"选项卡)。

image

注意:一旦您在IntelliSense中转到"全部"选项卡,在.NET框架中会有很多“内置”的方法。所以如果您想知道为什么图标看起来不同,现在你知道为什么了:-)。extension方法是向某个类型中添加额外功能的很棒的方式,它不必更改实际类的执行。

现在我们有时需要传递参数和/或从extension方法中返回值。在extension方法中,第一个之后的任何你指定的参数变为了调用方需要传递的参数。通过声明它们为Function,您也可以从extension方法中返回值。

 Imports System.Runtime.CompilerServices
  
 Module CommonValidation
     ''' <summary>
     ''' This is my extension method that still does nothing. :-)
     ''' </summary>
     ''' <param name="value">extend the string type</param>
     ''' <param name="param">demoing parameter</param>
     ''' <returns></returns>
     ''' <remarks></remarks>
     <Extension()>
     Public Function MyMethod(ByVal value As String, param As Boolean) As Boolean
         'Do something
         Return True
     End Function
 End Module

如果我们现在使用此extension方法,你将看到布尔值参数是必需的。

image

现在让我们开始迁移我们的验证方法。让我们从州验证方法开始。注意在此方法中,我们通过以大写格式输入首次设置格式。为了改变我们正在扩展的此类型的值,我们只需通过 ByRef 而不是 ByVal传递 参数。我也想要允许传递一个参数,来指示该字段是否可以为空。所以现在以下是我们与 IsState extension方法相对应的 CommonValidation 模块:

 Imports System.Runtime.CompilerServices
 Imports System.Text.RegularExpressions
  
 Module CommonValidation
     ''' <summary>
     ''' Checks if the string is formatted as a 2 character US state code
     ''' </summary>
     ''' <param name="state">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
     ''' <returns>True if the string is a valid US state, otherwise false</returns>
     ''' <remarks></remarks>
     <Extension()>
     Public Function IsState(ByRef state As String, ByVal isEmptyOK As Boolean) As Boolean
         If state <> "" Then
             'States should always be upper case
             state = state.ToUpper
  
             'Now validate based on regular expression pattern
             Dim pattern = "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|" +
                           "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|" +
                           "T[NX]|UT|V[AIT]|W[AIVY]))$"
  
             Return Regex.IsMatch(state, pattern)
         Else
             Return isEmptyOK
         End If
     End Function
 End Module

请注意,当我们调用extension 方法时,IntelliSense 在_Validate 方法中又回来了:

image

若要完成State_Validate 方法,我们所有需要做的是查看返回值以确定我们是否应该添加属性错误:

 Private Sub State_Validate(results As EntityValidationResultsBuilder)
     If Not Me.State.IsState(True) Then
         results.AddPropertyError("Please enter a valid US State.")
     End If
 End Sub
 
 好酷 !下面是所有模块中的扩展验证方法:
 
 Imports System.Runtime.CompilerServices
 Imports System.Text.RegularExpressions
 
 Module CommonValidation
     ''' <summary>
     ''' Checks if the string is formatted as a 2 character US state code
     ''' </summary>
     ''' <param name="state">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
     ''' <returns>True if the string is a valid US state, otherwise false</returns>
     ''' <remarks></remarks>
     <Extension()>
     Public Function IsState(ByRef state As String, ByVal isEmptyOK As Boolean) As Boolean
         If state <> "" Then
             'States should always be upper case
             state = state.ToUpper
  
             'Now validate based on regular expression pattern
             Dim pattern = "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|" +
                           "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|" +
                           "T[NX]|UT|V[AIT]|W[AIVY]))$"
  
             Return Regex.IsMatch(state, pattern)
         Else
 Return isEmptyOK
         End If
 End Function
     ''' <summary>
     ''' Checks if the string is formatted as a valid US ZIP code
     ''' </summary>
 ''' <param name="zip">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
     ''' <returns>True if the string is a valid ZIP code, otherwise false</returns>
     ''' <remarks></remarks>
     <Extension()>
     Public Function IsZIP(ByRef zip As String, ByVal isEmptyOK As Boolean) As Boolean
         If zip <> "" Then
             'Add the dash if the user didn't enter it and the ZIP code is 9 characters
             If Not zip.Contains("-") AndAlso zip.Length = 9 Then
                 zip = zip.Substring(0, 5) + "-" + zip.Substring(5)
             End If
             'Now validate based on regular expression pattern
             Return Regex.IsMatch(zip, "^\d{5}$|^\d{5}-\d{4}$")
 Else
             Return isEmptyOK
         End If
     End Function
     ''' <summary>
     ''' Checks if the string is formatted as a Social Security Number
     ''' </summary>
     ''' <param name="ssn">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
 ''' <returns>True if the string is a valid SSN, otherwise false</returns>
 
 ''' <remarks></remarks>
     <Extension()>
     Public Function IsSSN(ByRef ssn As String, ByVal isEmptyOK As Boolean) As Boolean
         If ssn <> "" Then
             'Add the dashes if the user didn't enter it and the SSN is 9 characters
             If Not ssn.Contains("-") AndAlso ssn.Length = 9 Then
                 ssn = ssn.Substring(0, 3) + "-" + ssn.Substring(3, 2) + "-" + ssn.Substring(5)
             End If
  
             'Now validate based on regular expression pattern
             Return Regex.IsMatch(ssn, "^\d{3}-\d{2}-\d{4}$")
         Else
             Return isEmptyOK
         End If
     End Function
  
     ''' <summary>
     ''' Checks if the string contains only upper and lower case letters
     ''' </summary>
     ''' <param name="value">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
     ''' <param name="isWhitespaceOK">True if spaces are allowed, otherwise false</param>
     ''' <returns></returns>
     ''' <remarks></remarks>
     <Extension()>
     Public Function IsAlpha(ByVal value As String,
  
                     ByVal isEmptyOK As Boolean,
                             ByVal isWhitespaceOK As Boolean) As Boolean
         If value <> "" Then
             'Validation for strings that must be Alphabetical characters only. 
             Dim pattern As String
             If isWhitespaceOK Then
                 'Allows spaces 
                 pattern = "^[a-zA-Z\s]+$"
             Else
    'No spaces
                 pattern = "^[a-zA-Z]+$"
             End If
  
             Return Regex.IsMatch(value, pattern)
         Else
             Return isEmptyOK
         End If
     End Function
  
     ''' <summary>
     ''' Checks if the string contains only upper and lower case letters and/or numbers
     ''' </summary>
     ''' <param name="value">string type extension</param>
     ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
     ''' <param name="isWhitespaceOK">True if spaces are allowed, otherwise false</param>
     ''' <returns></returns>
     ''' <remarks></remarks>
     <Extension()>

Public Function IsAlphaNumeric(ByVal value As String,

 ByVal isEmptyOK As Boolean,
                                    ByVal isWhitespaceOK As Boolean) As Boolean
         If value <> "" Then
             'Validation for strings that must be AlphaNumeric characters only. 
             Dim pattern As String
             If isWhitespaceOK Then
                 'Allows spaces 
   pattern = "^[a-zA-Z0-9\s]+$"
             Else
                 'No spaces
                 pattern = "^[a-zA-Z0-9]+$"
             End If
  
             Return Regex.IsMatch(value, pattern)
         Else
             Return isEmptyOK
         End If
     End Function
 End Module
 
 最后,我们的客户实体调用了这些方法。注意:现在代码看起来有多么清晰,我们都可以在应用程序中拒绝这些跨实体的方法。
 Namespace LightSwitchApplication
     Public Class Customer
  
         Private Sub State_Validate(results As EntityValidationResultsBuilder)
             If Not Me.State.IsState(True) Then
                 results.AddPropertyError("Please enter a valid US State.")
             End If
         End Sub
  
  
         Private Sub SSN_Validate(results As EntityValidationResultsBuilder)
             If Not Me.SSN.IsSSN(True) Then
                 results.AddPropertyError("Please enter a valid SSN (i.e. 123-45-6789).")
             End If
         End Sub
  
 Private Sub ZIP_Validate(results As EntityValidationResultsBuilder)
             If Not Me.ZIP.IsZIP(True) Then
                 results.AddPropertyError("Please enter a valid US ZIP code.")
             End If
         End Sub
  
         Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
             If Not Me.LastName.IsAlpha(False, True) Then
                 results.AddPropertyError("Last Name can only contain alphabetical characters.")
             End If
         End Sub
  
         Private Sub FirstName_Validate(results As EntityValidationResultsBuilder)
             If Not Me.FirstName.IsAlpha(True, True) Then
                 results.AddPropertyError("First Name can only contain alphabetical characters.")
  
        End If
 End Sub
     End Class
 End Namespace

为了把这些都放在一个具体的例子,下面包含了示代例码:

https://code.msdn.microsoft.com/Common-Validation-Rules-in-397bf46b

我希望这篇博客有助于您编写自己的更复杂的商务规则和 LightSwitch 应用程序的验证方法。

希望大家喜欢!