How to: Customize Data Field Validation in the Data Model Using Custom Attributes

ASP.NET Dynamic Data enables you to customize and extend data field validation at the data layer level. This topic shows how you can add data field validation in the data model by doing the following:

  • Creating a custom validation attribute. This attribute enables you to create custom metadata that you use in the data model for validation.

  • Applying the custom validation attribute. After the custom attribute is created, you apply it to the data fields that you want to validate.

See a run-time code example of this feature: Run.

Creating a Custom Validation Attribute

A custom validation attribute lets you create metadata that you can use in the data model to validate data fields. You must derive the custom attribute from the ValidationAttribute base class.

To create a custom validation attribute

  1. In Solution Explorer, right-click the App_Code folder, and then click Add New Item.

  2. Under Add New Item, click Class.

    In the Name box, enter the name of the custom validation attribute class. You can use any name that is not already being used. For example, you can enter the name CustomAttribute.cs in Visual C# or CustomAttribute.vb in Visual Basic in order to create a custom attribute class named CustomAttribute.

  3. Add references to the System, System.Globalization, and System.ComponentModel.DataAnnotations namespaces by using the Imports keyword in Visual Basic or the using keyword in Visual C#, as shown in the following example:

    using System;
    using System.Globalization;
    using System.ComponentModel.DataAnnotations;
    
    Imports System
    Imports System.Globalization
    Imports System.ComponentModel.DataAnnotations
    
  4. Make the following changes to the class definition:

    • Make the class not inheritable. Add the NotInheritable keyword in Visual Basic or the sealed keyword in Visual C#.

    • Derive the class from the ValidationAttribute base type.

    • Apply the AttributeUsageAttribute attribute to the class definition to state how your custom validation attribute must be used.

    The following example shows a class definition. The AttributeUsageAttribute parameters are set so that the custom validation attribute can be applied to properties or fields only one time.

    [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. Override the IsValid method and add logic to perform validation. Return true if the custom validation is successful, or false if it fails. The value to be validated is passed to the method as the only parameter.

    The following example shows the overridden method.

    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. Optionally, override the FormatErrorMessage method to perform custom error-message formatting.

    The following example shows how to use the name of the data field that failed the validation to build a custom error message. The ErrorMessageString value is passed as a parameter when the custom attribute is applied to the data field.

    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. Save and close the class attribute file.

Applying a Custom Validation Attribute

To customize validation for a data field you must implement a partial class that extends the data model. This enables you to apply the custom attribute to the data field.

To create a partial class for validation

  1. In Solution Explorer, right-click the App_Code folder, and then click Add New Item.

  2. Under Visual Studio installed templates, click Class.

    In the Name box, enter the name of the database table you want to add validation for.

    In the Name box, enter the name of the database table that you want to add validation for. For example, to add validation for the Customers table, enter "Customer" and name the class Customer.

  3. Add the Partial keyword in Visual Basic or the partial keyword in Visual C# to the class definition to make it a partial class.

  4. If you are creating the class in Visual C#, delete the default constructor.

    The following example shows the updated class declaration.

    public partial class Customer {
    
    }
    
    Partial Public Class Customer
    
    End Class
    
  5. Add references to the System.Web.DynamicData and System.ComponentModel.DataAnnotations namespaces by using the Imports keyword in Visual Basic or the using keyword in Visual C#, as shown in the following example:

    using System.Web.DynamicData;
    using System.ComponentModel.DataAnnotations;
    
    Imports System.Web.DynamicData
    Imports System.ComponentModel.DataAnnotations
    
  6. In the same file, create a second class that will act as the associated metadata class. You can use any name for the class that is a valid class name and that is not already being used.

    The following example shows a metadata class declaration.

    public class CustomerMetadata
    {
    
    }
    
    Public Class CustomerMetadata 
    
    End Class
    

    The associated metadata class provides an object that you can apply validation attributes to.

  7. Apply the MetadataTypeAttribute attribute to the partial class definition. For the attribute's parameter, use the name of the associated metadata class that you created in the previous step.

    The following example shows the partial-class definition with the attributed added.

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

You can now apply the custom validation attribute to a data field.

To apply a custom validation attribute to a data field

  1. In the metadata class, create a property or field whose name corresponds to the data field to validate.

  2. Apply the custom validation attribute that you created earlier to the data field you that you want to validate.

    The following example shows how to apply the custom validation attribute to the Phone data field.

    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. Save and close the class file.

Example

The following example shows how to create and apply a custom attribute named PhoneMaskAttribute to the Phone data field of the Customer table in the AdventureWorksLT database. The example uses a LINQ-to-SQL class for the data model.

The attribute instructs Dynamic Data to validate the Phone data field against a mask that represents a specific telephone number format. If the telephone number entered by the user does not match the mask, the attribute code issues a custom error.

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="https://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="https://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");

    }
}

Compiling the Code

To compile the example code, you need the following:

  • Microsoft Visual Studio 2008 Service Pack 1 or Visual Web Developer 2008 Express Edition Service Pack 1. 

  • The AdventureWorksLT sample database. For information about how to download and install the SQL Server sample database, see Microsoft SQL Server Product Samples: Database on the CodePlex site. Make sure that you install the correct version of the sample database for the version of SQL Server that you are running (Microsoft SQL Server 2005 or Microsoft SQL Server 2008).

  • A dynamic data-driven Web site. This enables you to create a data context for the database and to create the class that contains the data field to customize and the methods to override. For more information, see Walkthrough: Creating a New Dynamic Data Web Site using Scaffolding.

See Also

Reference

ValidationAttribute

DynamicValidator

Partial Classes and Methods (C# Programming Guide)

Concepts

ASP.NET Dynamic Data Scaffolding

ASP.NET Dynamic Data