Language preference:

Phase 2 / ValidationAttributes / Customizing ValidationAttributes at Runtime

Because ValidationAttributes are metadata, their objects cannot be modified when retrieved from properties. Sometimes you need to create or customize ValidationAttributes at runtime, such as comparing a date field to today’s date. Implement the PeterBlum.DES.DataAnnotations.ICustomizeDataField interface on your Entity class.

This interface defines a method that is passed a PeterBlum.DES.DataAnnotations.ActiveDataField object. Its properties define business rules about the DataField that you can modify, including validators.

In this example, it will be used to establish a DESDA.RangeAttribute on the Employee.BirthDate field so that dates are between 100 and 18 years from today's date. The call to activeDataField.GetEditableValidationAttribute() actually modifies activeDataField so its list of ValidationAttributes includes the desired ValidationAttribute.

public partial class Employee : DESDA.ICustomizeDataField
{
   public void CustomizeDataField(DESDA.ActiveDataField activeDataField)
   {
      switch (activeDataField.DataField)
      {
         case "BirthDate":
            DESDA.RangeAttribute vRangeAttribute = 
               activeDataField.GetEditableValidationAttribute<DESDA.RangeAttribute>(true);
            vRangeAttribute.Minimum = DateTime.Today.AddYears(-100);
            vRangeAttribute.Maximum = DateTime.Today.AddYears(-18);  // must be at least 18
            break;

      }  // switch
   }
}
public class EmployeeMetadata
{
   [DESDA.DateDataType()]
   [DESDA.Required()]
// Range is added by using the ICustomizeDataField interface on the Employee class
   public object BirthDate { get; set; }
}

The code files which implement ICustomizeDataField are shown in the Source Code Browser.

In the next topic, you'll learn about adding custom validation code.



Open the Source Code Browser (C# only)
Source Code Browser
 
/* ------------------------------------------------
 * Describes the Entity class for: Employee
 * Classes:
 *    Employee - Entity class. Edit it for validation and to customize metadata at runtime
 *    EmployeeMetadata - Entity Metadata class. It contains the DataAnnotations.
 *    EmployeeDAO - BLD DataAccessObject format version of a Data Access Object.
 *    
 * Requires .net 4.0 and these references:
 *    System.ComponentModel.DataAnnotations
 *    PeterBlum.DES
 *    PeterBlum.DES.DataAnnotations
 * Generated: 7/8/2011 4:18:55 PM
 * ------------------------------------------------*/
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using PeterBlum.DES.DAO.Attributes;
using PeterBlum.DES.DAO.EntityDAO;
// Some members of this namespace have identical names to those in System.ComponentModel.DataAnnotations
// making it easy to switch from one to the other by adding the "DESDA." prefix
using DESDA = PeterBlum.DES.DataAnnotations;

namespace PeterBlum.WithDataAnnotations
{
   // --- ENTITY CLASS --------------------------------------------------
   /// <summary>
   /// Entity class. Edit it for validation and to customize metadata at runtime
   /// </summary>
   [EntityDAOType(typeof(EmployeeDAO))]
   [MetadataType(typeof(EmployeeMetadata))]
   [DESDA.TableRestriction("Admin", DESDA.DenyAccess.None)]
   [DESDA.TableRestriction("Customer", DESDA.DenyAccess.All)]
   public partial class Employee : DESDA.ICustomizeDataField
   {
      [DESDA.ScaffoldColumn(false)]
      public string FullName
      {
         get { return FirstName + " " + LastName; }
      }
      [DESDA.ScaffoldColumn(false)]
      public string FullNameLastFirst
      {
         get { return LastName + ", " + FirstName; }
      }

      #region ICustomizeDataField Members
      /// <summary>
      /// Modify the business logic to account for runtime situations.
      /// Always consider pModifiableColumn.StaticColumn as a READ ONLY property.
      /// </summary>
      /// <param name="pModifiableColumn"></param>
      public void CustomizeDataField(DESDA.ActiveDataField activeDataField)
      {
         switch (activeDataField.DataField)
         {
            case "BirthDate":
               DESDA.RangeAttribute vRangeAttribute = activeDataField.GetEditableValidationAttribute<DESDA.RangeAttribute>(true);
               vRangeAttribute.Minimum = DateTime.Today.AddYears(-100);
               vRangeAttribute.Maximum = DateTime.Today.AddYears(-18);  // must be at least 18
               break;

         }  // switch

      }

      #endregion
   }  // class Employee

   // --- ENTITY METADATA --------------------------------------------------
   /// <summary>
   /// Entity Metadata class.
   /// Companion to the Employee Entity class that contains the DataAnnotations
   /// on properties with the same names as those in the actual Entity class.
   /// These properties do not require their types to match those in the Entity class.
   /// An Entity Metadata class allows the Entity class to be regenerated without
   /// overwriting DataAnnotations.
   /// </summary>
   [DESDA.InjectionSecurity(DetectScriptInjection=true, DetectSQLInjection=true,
      SQLDetectionLevel=PeterBlum.DES.Web.SQLDetectionLevel.MediumHigh)]  // impacts any string type field that does not have its own rules
   public class EmployeeMetadata
   {
      [DESDA.Required()]
      [DESDA.RegularExpression(@"\S\s+\S", CaseInsensitive=true, 
         ErrorMessage="Enter a building number and street name.")] // requires at least one space between data, so we get a street and building number  
      public object Address { get; set; }

      [DESDA.DateDataType()]
      [DESDA.Required()]
      [DESDA.Range()]   // actual values are assigned in Employee.CustomizeDataField method
      [DESDA.DisplayName("Birth date")]
      [DESDA.Filter(AutoGeneratePriority=DESDA.AutoGeneratePriority.Complete)]
      [DESDA.ScaffoldColumn(AfterThisDataField="Photo")]
      public object BirthDate { get; set; }

      [DESDA.Required()]
      [DESDA.Filter(InMultiFieldSearch=true)]
      public object City { get; set; }

      [DESDA.Required()]
      [DefaultValue("United States")]
      public object Country { get; set; }

      [DESDA.StringLength(4)]
      public object Extension { get; set; }

      [DESDA.Required()]
      [DESDA.DisplayName("First name")]
      [DESDA.Filter(InMultiFieldSearch=true)]
      public object FirstName { get; set; }

      [DESDA.DateDataType()]
      [DESDA.Required()]
      [DESDA.Filter()]
      [DESDA.DateRangeEntityFilterPicker(new PeterBlum.DES.QuickRange[] 
           {PeterBlum.DES.QuickRange.ThisMonth, 
            PeterBlum.DES.QuickRange.LastMonth,
            PeterBlum.DES.QuickRange.LastQuarter, 
            PeterBlum.DES.QuickRange.ThisYear,
            PeterBlum.DES.QuickRange.LastYear,
            PeterBlum.DES.QuickRange.NotSet, // assigned to 3 years ago
            PeterBlum.DES.QuickRange.NotSet},   // assigned to 1990s
         new string[] {
            "Label='3 years ago' StartDate-DateType='Relative' StartDate-PeriodType='Year' StartDate-PeriodOffset='-2' EndDate-DateType='Relative' EndDate-PeriodType='Year' EndDate-PeriodOffset='-2' EndDate-DayOffset='-1'",
            "Label='1990s' StartDate-ActualDate='1990-01-01' EndDate-ActualDate='1999-12-31'",
            "Label='2000s' StartDate-ActualDate='2000-01-01' EndDate-ActualDate='2009-12-31'"})]
      [DESDA.DisplayName("Date of hire")]
      [DESDA.ScaffoldColumn(AfterThisDataField="BirthDate")]
      public object HireDate { get; set; }

      [DESDA.DataType(DataType.PhoneNumber)]
      [DESDA.Required()]
      [DESDA.DisplayName("Phone")]
      [DESDA.Filter(DESDA.AutoGeneratePriority.Always, InMultiFieldSearch=true)]
      public object HomePhone { get; set; }

      [DESDA.Required()]
      [DESDA.DisplayName("Last name")]
      [DESDA.Filter(DESDA.AutoGeneratePriority.Always, InMultiFieldSearch=true)]
      public object LastName { get; set; }

      [DESDA.DataType(DataType.MultilineText)]
      [DESDA.Filter(InMultiFieldSearch=true)]
      public object Notes { get; set; }

      [DESDA.DbImageDataType()]
      public object Photo { get; set; }

      [DESDA.DataType(DataType.ImageUrl)]
      [DESDA.DisplayName("Url to Photo")]
      [DESDA.ScaffoldColumn(false)] // because this is extraneous with the same data in the Photo property
      public object PhotoPath { get; set; }

      [DESDA.Required()]
      [DESDA.DisplayName("Postal code")]
      public object PostalCode { get; set; }

      [DESDA.EnumeratedString("Mr.|Dr.|Mrs.|Hon.")]
      [DESDA.StringLength(10)]
      [DESDA.Filter(AutoGeneratePriority=PeterBlum.DES.DataAnnotations.AutoGeneratePriority.Complete)]
      [DESDA.DisplayName("Name prefix")]
      public object TitleOfCourtesy { get; set; }

      [DESDA.ScaffoldColumn(Private=true)]
      public object Employees { get; set; }

      [DESDA.DisplayName("Manager", ShortDisplayName="Mgr")]
      [DESDA.ForeignKeyQuery(DisplayDataFields="FullNameLastFirst")]
      [DESDA.ScaffoldColumn(AfterThisDataField="Notes")]
      public object Employee1 { get; set; }

      [DESDA.DisplayName("Territories")]
      [DESDA.ScaffoldColumn(false)] // because we use ScaffoldTable on the Territories table
      public object EmployeeTerritories { get; set; }

   }  // class EmployeeMetadata

   // --- BLD DATAACCESSOBJECT  --------------------------------------------------
   /// <summary>
   /// BLD DataAccessObject class for the Employee Entity class.
   /// It provides CRUD actions. The parent class already has default
   /// methods for Update(), Insert(), Delete() and several queries.
   /// </summary>
   /// <remarks>
   /// <para>For documentation, see the BLD DataAccessObject section of the Business Logic User's Guide.</para>
   /// </remarks>
   [TableName("Employees")]
   public class EmployeeDAO : LINQtoSQLEntityDAO<Employee>
   {
      public EmployeeDAO() : base(typeof(NorthWindDataContext)) { }
      public EmployeeDAO(object pDataContext) : base(pDataContext) { }

   }  // class EmployeeDAO

}