DataAnnotations In Depth

Introduction

The namespace System.ComponentModel.DataAnnotations, has a group of classes, attributes and methods, to make validations in our .NET applications.

In the Microsoft world, there are technologies such as WPF, Silverlight, ASP MVC, Entity Framework, etc., which make automatic validation with class and exclusive attributes. We think this mechanism is exclusive of these technologies, but it is not like this. We can use it with all classes of Framework.

Add restriction to our classes

The way of adding restrictions to our classes is by attributes in the properties.

  1. public class Customer  
  2. {  
  3.    
  4.     [Required]  
  5.     public string   Name                 { get; set; }  
  6.     public DateTime EntryDate            { get; set; }  
  7.     public string   Password             { get; set; }  
  8.     public string   PasswordConfirmation { get; set; }  
  9. }

In this example, we have used a Required attribute, but there are more, as we will discuss later.

Validation Attributes Available

All attributes in this section inherits of abstract class ValidationAttribute.

  • ValidationAttribute validate only one Property in the object.
  • ValidationAttribute, has an important property, ErrorMessage. This property get or set the custom validation message in case of error.
  • ErrorMessage has an implicit ‘FormatString’, like System.Console.Write or System.Console.WriteLine methods, concerning the use of "{0}{1}{2} … {n}" parameters.
Example
  1. public class TestClass  
  2. {  
  3.     public string PropertyOne { get; set; }  
  4.     [MaxLength(2, ErrorMessage = "The property {0} doesn't have more than {1} elements")]  
  5.     public int[] ArrayInt { get; set; }  
  6.    
  7. }

This is the error validation message.



The sequence of parameters (for ‘StringFormat’) will be the next.

{0} - PropertyName
{1} - Parameter 1
{2} - Parameter 2

{n} - Parameter n

We will study the list of validation attributes available.

CompareAttribute

This attribute, compares the value of two properties. More info in Link.

  1. public class Customer  
  2. {  
  3.     public string   Name      { get; set; }  
  4.     public DateTime EntryDate { get; set; }  
  5.     public string   Password  { get; set; }  
  6.     [Compare("Customer.Password", ErrorMessage = "The fields Password and PasswordConfirmation should be equals")]  
  7.     public string   PasswordConfirmation { get; set; }  

This attribute, compares the property marked, with the property linked in its first parameter by a string argument.

DateTypeAttribute

This attribute allows marking one property/field way more specifically than .Net types. MSDN says: DateTypeAttribute specifies the name of an additional type to associate with a data field. Link.

In applications (ASP MVC, Silverlight, etc), with templates, can be used to changed display data format. For example one property market with DateTypeAttribute to Password, show its data in a TextBox with “*” character.

  1. public class Customer  
  2. {  
  3.     public string Name { get; set; }  
  4.     public DateTime EntryDate { get; set; }  
  5.   
  6.     [DataType(DataType.Password)]  
  7.     public string Password { get; set; }  
  8.   
  9.     public string PasswordConfirmation { get; set; }  

The enum DataType has the following values.

  • Custom = 0
  • DateTime = 1,
  • Date = 2,
  • Time = 3,
  • Duration = 4,
  • PhoneNumber = 5,
  • Currency = 6,
  • Text = 7,
  • Html = 8,
  • MultilineText = 9,
  • EmailAddress = 10,
  • Password = 11,
  • Url = 12,
  • ImageUrl = 13,
  • CreditCard = 14,
  • PostalCode = 15,
  • Upload = 16,

StringLenghtAttribute

Marked the max and the min length of characters allowed in the property/field. Link.

  1. public class Customer  
  2. {   
  3.     [StringLength(maximumLength: 50  , MinimumLength = 10,  
  4.         ErrorMessage = "The property {0} should have {1} maximum characters and {2} minimum characters")]  
  5.     public string Name { get; set; }  
  6.   
  7.     public DateTime EntryDate { get; set; }  
  8.     public string Password { get; set; }  
  9.     public string PasswordConfirmation { get; set; }  

MaxLengthAttribute and MinLengthAttribute

These attributes were added to Entity Framework 4.1.

Specify the number of maximum and minimum elements in the Array property. This is valid for string properties, because one string is a char[] too. More information (MaxLengthAttribute, MinLengthAttribute).

In this example, we can see the two types: for string and for array.

  1. public class Customer  
  2. {   
  3.     [MaxLength(50, ErrorMessage = "The {0} can not have more than {1} characters")]  
  4.     public string Name { get; set; }  
  5.    
  6.     public DateTime EntryDate { get; set; }  
  7.     [DataType(DataType.Password)]  
  8.     public string Password { get; set; }  
  9.     [Compare("Customer.Password", ErrorMessage = "The fields Password and PasswordConfirmation should be equals")]  
  10.     public string PasswordConfirmation { get; set; }  
  11.    
  12.    
  13.     [MaxLength(2, ErrorMessage = "The property {0} can not have more than {1} elements")]  
  14.     public int[] EjArrayInt { get; set; }  

In the case of Array property, this properties should be Array, are not valid: List<T>, Collection<T>, etc.

RequieredAttribute

Specify that the field is mandatory and it doesn’t contain a null or string.Empty values. Link.

  1. public class Customer  
  2. {  
  3.    
  4.     [Required (ErrorMessage = "{0} is a mandatory field")]  
  5.     public string Name { get; set; }  
  6.   
  7.     public DateTime EntryDate { get; set; }  
  8.     public string Password { get; set; }  
  9.     public string PasswordConfirmation { get; set; }  

RangeAttribute

Specify a range value for a data field. Link.

  1. public class Customer  
  2. {  
  3.     public string Name { get; set; }  
  4.    
  5.     [Range(typeof(DateTime), "01/01/1900""01/01/2014",  
  6.     ErrorMessage = "Valid dates for the Property {0} between {1} and {2}")]  
  7.     public DateTime EntryDate { get; set; }  
  8.    
  9.     public string Password { get; set; }  
  10.     [Compare("Customer.Password", ErrorMessage = "The fields Password and PasswordConfirmation should be equals")]  
  11.     public string PasswordConfirmation { get; set; }  
  12.    
  13.     [Range(0, 150, ErrorMessage = "The Age should be between 0 and 150 years")]  
  14.     public int Age { get; set; }  

For the EntryDate property, the first argument in the RangeAttribute is the typeof of Property. For the Age property it isn’t necessary. It is mandatory to designate the typeof data in the first argument, whenever the property isn’t a numerical type. If we marked any property not numeric with a RangeAttribute and don't specify this parameter, the project won't compiler



CustomValidationAttributes

Specify a custom validate method. Link.

For this ValidationAttribute, we build a new class with a static method and this signature: public static ValidationResult MethodValidate( same_type_property_to_validate artument)

Example

  1. public class CustomerWeekendValidation  
  2. {  
  3.     public static ValidationResult WeekendValidate(DateTime date)  
  4.     {  
  5.         return date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday  
  6.             ? new ValidationResult("The wekeend days aren't valid")  
  7.             : ValidationResult.Success;  
  8.     }  

For the property marked, we marked the property with CustomValidate, and add two arguments.

  1. TypeOf our CustomValidationClass.
  2. Validator MethodName of our CustomValidationClass. This parameter is a string type.
  1. public class Customer  
  2. {  
  3.     public string Name { get; set; }  
  4.    
  5.     [CustomValidation(typeof(CustomerWeekendValidation), nameof(CustomerWeekendValidation.WeekendValidate))]  
  6.     public DateTime EntryDate { get; set; }  
  7.    
  8.     public string Password { get; set; }  
  9.     public string PasswordConfirmation { get; set; }  
  10.     public int Age { get; set; }  

Customs Attributes (inherits ValidationAttribute)

This is the last option, it is not an available attribute in the DataAnnotations namespace. Customs Attributes are classes build from scratch, inherits of ValidationAttribute.

In this class we have the freedom to create: constructors, properties, etc.

The first step will be to write a new class that  inherits ValidationAttribute and overwrite IsValid Method:

Example

  1. public class ControlDateTimeAttribute : ValidationAttribute  
  2. {  
  3.     private DayOfWeek[] NotValidDays;  
  4.     private bool        ThrowExcepcion;  
  5.    
  6.     public ControlDateTimeAttribute(params DayOfWeek[] notValidDays)  
  7.     {  
  8.         ThrowExcepcion = false;  
  9.         NotValidDays   = notValidDays;  
  10.     }  
  11.    
  12.     public ControlDateTimeAttribute(bool throwExcepcion, params DayOfWeek[] notValidDays)  
  13.     {  
  14.         ThrowExcepcion = throwExcepcion;  
  15.         NotValidDays   = notValidDays;  
  16.     }  
  17.    
  18.    
  19.     public override bool IsValid(object value)  
  20.     {  
  21.         DateTime fecha;  
  22.    
  23.         if (!DateTime.TryParse(value.ToString(), out fecha))  
  24.         {  
  25.             if (ThrowExcepcion)  
  26.                 throw new ArgumentException(  
  27.                     "The ControlDateTimeAttribute, only validate DateTime Types.");  
  28.             else  
  29.                 return false;  
  30.         }  
  31.    
  32.         return NotValidDays.Contains(fecha.DayOfWeek);  
  33.     }  

This class allows you to select invalid days in the week.

The last step is marking DateTime property with the new attribute.

  1. public class Customer  
  2. {  
  3.     public string Name { get; set; }  
  4.    
  5.     [ControlDateTime(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,ErrorMessage = "The {0} isn't valid")]  
  6.     public DateTime EntryDate { get; set; }  
  7.    
  8.     public string Password { get; set; }  
  9.     public string PasswordConfirmation { get; set; }  
  10.     public int Age { get; set; }  

Considerations and Tests

That was all for the first article of DataAnnotations, in the next chapter we will go deeper in the way of validation. We will leave an Extension Method for validation and test all previous examples.

Extension Method

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ComponentModel.DataAnnotations;  
  6.    
  7. namespace DataAnnotations  
  8. {  
  9.     public static class Extensiones  
  10.     {  
  11.         public static string ToDescErrorsString(this IEnumerable<ValidationResult> source, string messageEmptyCollection = null)  
  12.         {  
  13.             StringBuilder result = new StringBuilder();  
  14.    
  15.             if (source.Count() > 0)  
  16.             {  
  17.                 result.AppendLine("We found the next validations errors:");  
  18.                 source.ToList()  
  19.                     .ForEach(  
  20.                         s =>  
  21.                             result.AppendFormat("  {0} --> {1}{2}", s.MemberNames.FirstOrDefault(), s.ErrorMessage,  
  22.                                 Environment.NewLine));  
  23.             }  
  24.             else  
  25.                 result.AppendLine(messageEmptyCollection ?? string.Empty);  
  26.    
  27.             return result.ToString();  
  28.         }  
  29.    
  30.    
  31.         public static IEnumerable<ValidationResult> ValidateObject(this object source)  
  32.         {  
  33.             ValidationContext valContext = new ValidationContext(source, nullnull);  
  34.             var result     = new List<ValidationResult>();  
  35.             Validator.TryValidateObject(source, valContext, result, true);  
  36.    
  37.             return result;  
  38.         }   
  39.    
  40.     }  
  41.    

Validation Example

  1. using System;  
  2. using Microsoft.VisualStudio.TestTools.UnitTesting;  
  3. using DataAnnotationsLib.Extensions;  
  4. using System.ComponentModel.DataAnnotations;  
  5. using System.Linq;  
  6.    
  7. namespace DataAnnotationsLib.Tests  
  8. {  
  9.     [TestClass]  
  10.     public class UnitTest1  
  11.     {  
  12.         [TestMethod]  
  13.         public void TextClass_MaxLenght_ArrayIntProperty_NotValid()  
  14.         {  
  15.             TestClass textclass = new TestClass();  
  16.    
  17.             textclass.ArrayInt = new int[] { 0, 1, 2 };  
  18.    
  19.             var errors = textclass.ValidateObject();  
  20.    
  21.             Assert.IsTrue(errors.Any());  
  22.             Assert.AreEqual(errors.First().ErrorMessage, $"The property {nameof(textclass.ArrayInt)} doesn't have more than 2 elements");  
  23.         }  
  24.    
  25.    
  26.         [TestMethod]  
  27.         public void TextClass_MaxLenght_ArrayIntProperty_Valid()  
  28.         {  
  29.             TestClass textclass = new TestClass();  
  30.    
  31.             textclass.ArrayInt = new int[] { 0, 1};  
  32.    
  33.             var errors = textclass.ValidateObject();  
  34.    
  35.             Assert.IsFalse(errors?.Any() ?? false);  
  36.         }  
  37.    
  38.     }  
  39.    
  40.     public class TestClass  
  41.     {  
  42.         public string PropertyOne { get; set; }  
  43.         [MaxLength(2, ErrorMessage = "The property {0} doesn't have more than {1} elements")]  
  44.         public int[] ArrayInt { get; set; }  
  45.    
  46.     }  

The next article will be part II of DataAnnotations.