如何:使用自定义属性在数据模型中自定义数据字段验证

更新: 2008 年 7 月

ASP.NET 动态数据允许您在数据层级别自定义和扩展数据字段验证。本主题介绍如何通过执行以下操作来在数据模型中添加数据字段验证:

  • 创建自定义验证属性。此属性允许您创建在数据模型中用于进行验证的自定义元数据。

  • 应用自定义验证属性。创建自定义属性后,将它应用于要验证的数据字段。

运行此功能的在线示例。

创建自定义验证属性

利用自定义验证属性,可以创建在数据模型中用于验证数据字段的元数据。必须从 ValidationAttribute 基类派生自定义属性。

创建自定义验证属性

  1. 在**“解决方案资源管理器”中右击 App_Code 文件夹,再单击“添加新项”**。

  2. 在**“添加新项”下方单击“类”**。

    在**“名称”**框中输入自定义验证属性类的名称。可以使用任何尚未使用的名称。例如,可以在 Visual C# 中输入名称 CustomAttribute.cs,或者在 Visual Basic 中输入 CustomAttribute.vb,以创建名为 CustomAttribute 的自定义属性类。

  3. 在 Visual Basic 中使用 Imports 关键字或在 Visual C# 中使用 using 关键字添加对 System、System.Web.Globalization 和 System.ComponentModel.DataAnnotations 命名空间的引用,如下面的示例所示:

    using System;
    using System.Globalization;
    using System.ComponentModel.DataAnnotations;
    
    Imports System
    Imports System.Globalization
    Imports System.ComponentModel.DataAnnotations
    
  4. 对类定义进行以下更改:

    • 使类不可继承。在 Visual Basic 中添加 NotInheritable 关键字,或者在 Visual C# 中添加 sealed 关键字。

    • ValidationAttribute 基类型派生类。

    • AttributeUsageAttribute 属性应用于类定义,以指明必须如何使用自定义验证属性。

    下面的示例演示类定义。设置 AttributeUsageAttribute 参数,以便仅可以对属性 (Property) 或字段应用一次自定义验证属性 (Attribute)。

    [AttributeUsage(AttributeTargets.Property | 
      AttributeTargets.Field, AllowMultiple = false)]
    sealed public class CustomAttribute : ValidationAttribute
    {
    
    }
    
    <AttributeUsage(AttributeTargets.[Property] Or _
        AttributeTargets.Field, AllowMultiple := False)> _
    Public NotInheritable Class CustomAttribute
        Inherits ValidationAttribute
    ....
    End Class
    
  5. 重写 IsValid 方法,添加用于执行验证的逻辑。如果自定义验证成功,则返回 true;如果失败,则返回 false。要验证的值作为唯一的参数传递给该方法。

    下面的示例演示重写的方法。

    public override bool IsValid(object value)
    {
      bool result = true;
      // Add validation logic here.
      return result;
    }
    
    Public Overrides Function IsValid( _
        ByVal value As Object) As Boolean
          ' Add validation logic here.
      Return result
    End Function
    
  6. (可选)重写 FormatErrorMessage 方法以执行自定义错误消息格式设置。

    下面的示例演示如何使用验证失败的数据字段的名称来生成自定义的错误消息。自定义属性应用于数据字段时,ErrorMessageString 值作为参数传递。

    public override string FormatErrorMessage(string name)
    {
      return String.Format(CultureInfo.CurrentCulture, 
        ErrorMessageString, name);
    }
    
    Public Overrides Function FormatErrorMessage( _
        ByVal name As String) As String
          Return [String].Format(CultureInfo.CurrentCulture, _
            ErrorMessageString, name)
    End Function
    
  7. 保存并关闭类属性文件。

应用自定义验证属性

若要自定义数据字段的验证,必须实现一个扩展数据模型的分部类。这使您能够将自定义属性应用于数据字段。

创建用于验证的分部类

  1. 在**“解决方案资源管理器”中右击 App_Code 文件夹,再单击“添加新项”**。

  2. 在**“Visual Studio 已安装的模板”下方单击“类”**。

    在**“名称”**框中输入要添加验证的数据库表的名称。

    类名必须与表示表的实体类名匹配。例如,如果要向 Customers 表添加验证,则在 Visual C# 中必须将文件命名为 Customer.cs,或者在 Visual Basic 中必须将文件命名为 Customer.vb,并且将类命名为 Customer。

  3. 在 Visual Basic 中将 Partial 关键字添加到类定义中,或者在 Visual C# 中将 partial 关键字添加到类定义中,以使类成为分部类。

  4. 如果是在 Visual C# 中创建该类,请删除默认构造函数。

    下面的示例演示更新后的类声明。

    public partial class Customer {
    
    }
    
    Partial Public Class Customer
    
    End Class
    
  5. 在 Visual Basic 中使用 Imports 关键字或者在 Visual C# 中使用 using 关键字添加对 System.Web.DynamicDataSystem.ComponentModel.DataAnnotations 命名空间的引用,如下面的示例所示:

    using System.Web.DynamicData;
    using System.ComponentModel.DataAnnotations;
    
    Imports System.Web.DynamicData
    Imports System.ComponentModel.DataAnnotations
    
  6. 在同一个文件中,创建将充当关联元数据类的另一个类。可以为该类指定任何有效且尚未使用的类名。

    下面的示例演示元数据类声明。

    public class CustomerMetadata
    {
    
    }
    
    Public Class CustomerMetadata 
    
    End Class
    

    关联元数据类提供一个对象,您可以将验证属性应用于该对象。

  7. MetadataTypeAttribute 属性应用于分部类定义。对于此属性的参数,请使用上一步中创建的关联元数据类的名称。

    下面的示例演示添加了此属性的分部类定义。

    [MetadataType(typeof(CustomerMetadata))]
    public partial class Customer {
    
    }
    
    <MetadataType(GetType(CustomerMetadata))> _
    Partial Public Class Customer
    
    End Class
    

现在,可以将自定义验证属性应用于数据字段。

将自定义验证属性应用于数据字段

  1. 在元数据类中,创建一个其名称与要验证的数据字段相对应的属性或字段。

  2. 将之前创建的自定义验证属性应用于要验证的数据字段。

    下面的示例演示如何将自定义验证属性应用于 Phone 数据字段。

    public partial class CustomerMetadata
    {
      [CustomAttribute(parm1,
        ErrorMessage = "{0} field validation failed.")]
      public object Phone; 
    }
    
    Public Class CustomerMetadata 
      <PhoneMask(parm1, _
        ErrorMessage:="{0} field validation failed.")> _
      Public Phone As Object
    End Class
    
  3. 保存并关闭类文件。

示例

下面的示例演示如何创建名为 PhoneMaskAttribute 的自定义属性,并将该属性应用于 AdventureWorksLT 数据库中 Customer 表的 Phone 数据字段。本示例对数据模型使用 LINQ-to-SQL 类。

此属性指示动态数据根据表示特定电话号码格式的掩码来验证 Phone 数据字段。如果用户输入的电话号码与掩码不匹配,属性代码会引发自定义错误。

Imports Microsoft.VisualBasic
Imports System
Imports System.Globalization
Imports System.ComponentModel.DataAnnotations

<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, AllowMultiple:=False)> _
Public NotInheritable Class PhoneMaskAttribute
    Inherits ValidationAttribute

    ' Internal field to hold the mask value.
    ReadOnly _mask As String
    Public ReadOnly Property Mask() As String
        Get
            Return _mask
        End Get
    End Property

    Public Sub New(ByVal mask As String)
        _mask = mask
    End Sub

    Public Overrides Function IsValid( _
    ByVal value As Object) As Boolean
        Dim phoneNumber As String = DirectCast(value, String)
        Dim result As Boolean = True
        If Me.Mask <> Nothing Then
            result = MatchesMask(Me.Mask, phoneNumber)
        End If
        Return result
    End Function

    ' Checks if the entered phone number matches the mask.
    Public Shared Function MatchesMask(ByVal mask As String, _
        ByVal phoneNumber As String) As Boolean
        If mask.Length <> phoneNumber.Trim().Length Then
            ' Length mismatch.
            Return False
        End If
        Dim i As Integer = 0
        While i < mask.Length
            If mask(i) = "d"c _
             AndAlso Char.IsDigit(phoneNumber(i)) = False Then
                ' Digit expected at this position.      
                Return False
            End If
            If mask(i) = "-"c AndAlso phoneNumber(i) <> "-"c Then
                ' Spacing character expected at this position.
                Return False
            End If
            System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
        End While
        Return True
    End Function

    Public Overrides Function FormatErrorMessage( _
    ByVal name As String) As String
        Return [String].Format(CultureInfo.CurrentCulture, _
          ErrorMessageString, name, Me.Mask)
    End Function


End Class

using System;
using System.Globalization;
using System.ComponentModel.DataAnnotations;


[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class PhoneMaskAttribute : ValidationAttribute
{
    // Internal field to hold the mask value.
    readonly string _mask;

    public string Mask
    {
        get { return _mask; }
    }

    public PhoneMaskAttribute(string mask)
    {
        _mask = mask;
    }


    public override bool IsValid(object value)
    {
        var phoneNumber = (String)value;
        bool result = true;
        if (this.Mask != null)
        {
            result = MatchesMask(this.Mask, phoneNumber);
        }
        return result;
    }

    // Checks if the entered phone number matches the mask.
    internal bool MatchesMask(string mask, string phoneNumber)
    {
        if (mask.Length != phoneNumber.Trim().Length)
        {
            // Length mismatch.
            return false;
        }
        for (int i = 0; i < mask.Length; i++)
        {
            if (mask[i] == 'd' && char.IsDigit(phoneNumber[i]) == false)
            {
                // Digit expected at this position.
                return false;
            }
            if (mask[i] == '-' && phoneNumber[i] != '-')
            {
                // Spacing character expected at this position.
                return false;
            }
        }
        return true;
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture,
          ErrorMessageString, name, this.Mask);
    }

}
Imports Microsoft.VisualBasic
Imports System.Web.DynamicData
Imports System.ComponentModel.DataAnnotations

<MetadataType(GetType(CustomerMetadata))> _
Partial Public Class Customer

End Class

Public Class CustomerMetadata
    <PhoneMask("999-999-9999", _
    ErrorMessage:="{0} field value does not match the mask {1}.")> _
  Public Phone As Object

End Class
using System.Web.DynamicData;
using System.ComponentModel.DataAnnotations;

[MetadataType(typeof(CustomerMetadata))]
public partial class Customer
{

}

public class CustomerMetadata
{
    [PhoneMask("999-999-9999",
        ErrorMessage = "{0} value does not match the mask {1}.")]
    public object Phone; 
}
<%@ Page Language="VB" 
AutoEventWireup="true" CodeFile="CustomAttributeValidation.aspx.vb" 
Inherits="CustomAttributeValidation" %>


<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
    <link href="~/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
     <h2>Example: <%=Title%></h2>

     <!-- Enable dynamic behavior. The GridView must be 
     registered with the manager. See code-behind file. -->
    <asp:DynamicDataManager ID="DynamicDataManager1" runat="server"
        AutoLoadForeignKeys="true" />


    <form id="form1" runat="server">

        <!-- Capture validation exceptions -->
        <asp:DynamicValidator ID="ValidatorID" ControlToValidate="GridView1" 
            runat="server" /> 

        <asp:GridView ID="GridView1" 
            runat="server" 
            DataSourceID="GridDataSource" 
            AutoGenerateColumns="false"  
            AutoGenerateEditButton="true"
            AllowPaging="true"
            PageSize="10"
            AllowSorting="true">
            <Columns>
                <asp:DynamicField DataField="FirstName" />
                <asp:DynamicField DataField="LastName" />
                <asp:DynamicField DataField="Phone" />
            </Columns>
       </asp:GridView>
    </form>

    <!-- Connect to the database -->
    <asp:LinqDataSource ID="GridDataSource" runat="server"  
        TableName="Customers" EnableUpdate="true"
        ContextTypeName="AdventureWorksLTDataContext">
    </asp:LinqDataSource>
</body>
</html>
<%@ Page Language="C#" 
AutoEventWireup="true" CodeFile="CustomAttributeValidation.aspx.cs" 
Inherits="CustomAttributeValidation" %>


<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
    <link href="~/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
     <h2>Example: <%=Title%></h2>

     <!-- Enable dynamic behavior. The GridView must be 
     registered with the manager. See code-behind file. -->
    <asp:DynamicDataManager ID="DynamicDataManager1" runat="server"
        AutoLoadForeignKeys="true" />


    <form id="form1" runat="server">

        <!-- Capture validation exceptions -->
        <asp:DynamicValidator ID="ValidatorID" ControlToValidate="GridView1" 
            runat="server" /> 

        <asp:GridView ID="GridView1" 
            runat="server" 
            DataSourceID="GridDataSource" 
            AutoGenerateColumns="false"  
            AutoGenerateEditButton="true"
            AllowPaging="true"
            PageSize="10"
            AllowSorting="true">
            <Columns>
                <asp:DynamicField DataField="FirstName" />
                <asp:DynamicField DataField="LastName" />
                <asp:DynamicField DataField="Phone" />
            </Columns>
       </asp:GridView>
    </form>

    <!-- Connect to the database -->
    <asp:LinqDataSource ID="GridDataSource" runat="server"  
        TableName="Customers" EnableUpdate="true"
        ContextTypeName="AdventureWorksLTDataContext">
    </asp:LinqDataSource>
</body>
</html>
Imports System
Imports System.Collections
Imports System.Configuration
Imports System.Web.DynamicData

Partial Public Class CustomAttributeValidation
    Inherits System.Web.UI.Page
    Protected _table As MetaTable

    Protected Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs)
        ' Register control with the data manager.
        DynamicDataManager1.RegisterControl(GridView1)
    End Sub

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        ' Get the table entity.
        _table = GridDataSource.GetTable()

        ' Assign title dynamically.
        Title = String.Concat( _
        "Customize <i>Phone</i> Data Field Validation", _
        "Using a Custom Attribute")
    End Sub
End Class
using System;
using System.Collections;
using System.Configuration;
using System.Web.DynamicData;

public partial class CustomAttributeValidation : System.Web.UI.Page
{
    protected MetaTable _table;

    protected void Page_Init(object sender, EventArgs e)
    {
        // Register control with the data manager.
        DynamicDataManager1.RegisterControl(GridView1);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        // Get the table entity.
        _table = GridDataSource.GetTable();

        // Assign title dynamically.
        Title = string.Concat("Customize <i>Phone</i> Data Field Validation",
            "Using a Custom Attribute");

    }
}

编译代码

若要编译示例代码,需要以下组件:

  • Microsoft Visual Studio 2008 Service Pack 1 或 Visual Web Developer 2008 速成版 Service Pack 1。

  • AdventureWorksLT 示例数据库。有关如何下载和安装 SQL Server 示例数据库的信息,请参见 CodePlex 站点上的 Microsoft SQL Server Product Samples: Database(Microsoft SQL Server 产品示例:数据库)。请确保针对所运行的 SQL Server 版本(Microsoft SQL Server 2005 或 Microsoft SQL Server 2008)安装了正确版本的示例数据库。

  • 动态数据驱动的网站。这使您能够为数据库创建数据上下文,以及创建包含要自定义的数据字段和要重写的方法的类。有关更多信息,请参见Walkthrough: Creating a New Dynamic Data Web Site using Scaffolding

请参见

概念

ASP.NET 动态数据字段模板概述

ASP.NET 动态数据模型概述

ASP.NET 动态数据概述

参考

ValidationAttribute

DynamicValidator

分部类和方法(C# 编程指南)

修订记录

日期

修订历史记录

原因

2008 年 7 月

新增了主题。

SP1 功能更改。