domingo, 27 de noviembre de 2016

DataAnnotations In Depth I




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

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









Add restriction to our classes  

The way of add restrictions to our classes is by attributes in the properties:


public class Customer
{
    [Required]
    public string   Name                 { get; set; }
    public DateTime EntryDate            { get; set; }
    public string   Password             { get; set; }
    public string   PasswordConfirmation { get; set; }
}



In this example, we have used a Required attribute, but there are more, as we will discussed 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:

public class TestClass
{
    public string PropertyOne { get; set; }
    [MaxLength(2, ErrorMessage = "The property {0} doesn't have more than {1} elements")]
    public int[] ArrayInt { get; set; }
 
}


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.

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


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

DateTypeAttribute
This attribute allows mark one property/field of way more specific 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.


public class Customer
{
    public string Name { get; set; }
    public DateTime EntryDate { get; set; }

    [DataType(DataType.Password)]
    public string Password { get; set; }

    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.

public class Customer
{
    [StringLength(maximumLength: 50  , MinimumLength = 10,
        ErrorMessage = "The property {0} should have {1} maximum characters and {2} minimum characters")]
    public string Name { get; set; }

    public DateTime EntryDate { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmation { get; set; }
}



MaxLengthAttribute and MinLengthAttribute
These attributes were added to Entity Framework 4.1.
Specify the number 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:


public class Customer
{
    [MaxLength(50, ErrorMessage = "The {0} can not have more than {1} characters")]
    public string Name { get; set; }
 
    public DateTime EntryDate { get; set; }
    [DataType(DataType.Password)]
    public string Password { get; set; }
    [Compare("Customer.Password", ErrorMessage = "The fields Password and PasswordConfirmation should be equals")]
    public string PasswordConfirmation { get; set; }
 
 
    [MaxLength(2, ErrorMessage = "The property {0} can not have more than {1} elements")]
    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

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


public class Customer
{
    [Required (ErrorMessage = "{0} is a mandatory field")]
    public string Name { get; set; }

    public DateTime EntryDate { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmation { get; set; }
}



RangeAttribute

Specified a range values for a data field. Link.


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



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



















CustomValidationAttributes
Specified 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:

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


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.


public class Customer
{
    public string Name { get; set; }
 
    [CustomValidation(typeof(CustomerWeekendValidation), nameof(CustomerWeekendValidation.WeekendValidate))]
    public DateTime EntryDate { get; set; }
 
    public string Password { get; set; }
    public string PasswordConfirmation { get; set; }
    public int Age { get; set; }
}



Customs Attributes (inherits ValidationAttribute)
This is the last option, it is not available attribute in the DataAnnotations namespace. Customs Attributes are classes build from scratch, inherits of ValidationAttribute.
In this classes we have the freedom to create: constructors, properties, etc.
The first step will be write a new class inherits ValidationAttribute and overwrite IsValid Method:

Example:


public class ControlDateTimeAttribute : ValidationAttribute
{
    private DayOfWeek[] NotValidDays;
    private bool        ThrowExcepcion;
 
    public ControlDateTimeAttribute(params DayOfWeek[] notValidDays)
    {
        ThrowExcepcion = false;
        NotValidDays   = notValidDays;
    }
 
    public ControlDateTimeAttribute(bool throwExcepcion, params DayOfWeek[] notValidDays)
    {
        ThrowExcepcion = throwExcepcion;
        NotValidDays   = notValidDays;
    }
 
 
    public override bool IsValid(object value)
    {
        DateTime fecha;
 
        if (!DateTime.TryParse(value.ToString(), out fecha))
        {
            if (ThrowExcepcion)
                throw new ArgumentException(
                    "The ControlDateTimeAttribute, only validate DateTime Types.");
            else
                return false;
        }
 
        return NotValidDays.Contains(fecha.DayOfWeek);
    }
}


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

The las step is market DateTime property with the new attribute:

public class Customer
{
    public string Name { get; set; }
 
    [ControlDateTime(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,ErrorMessage = "The {0} isn't valid")]
    public DateTime EntryDate { get; set; }
 
    public string Password { get; set; }
    public string PasswordConfirmation { get; set; }
    public int Age { get; set; }
}




Considerations and Tests
That was all for the first article of DataAnnotations, in the next chapter we will deepen in the way of validate. We will leave an Extension Method for validation and tests all previous examples.

Extension Method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
 
namespace DataAnnotations
{
    public static class Extensiones
    {
        public static string ToDescErrorsString(this IEnumerable<ValidationResult> source, string messageEmptyCollection = null)
        {
            StringBuilder result = new StringBuilder();
 
            if (source.Count() > 0)
            {
                result.AppendLine("We found the next validations errors:");
                source.ToList()
                    .ForEach(
                        s =>
                            result.AppendFormat("  {0} --> {1}{2}", s.MemberNames.FirstOrDefault(), s.ErrorMessage,
                                Environment.NewLine));
            }
            else
                result.AppendLine(messageEmptyCollection ?? string.Empty);
 
            return result.ToString();
        }
 
 
        public static IEnumerable<ValidationResult> ValidateObject(this object source)
        {
            ValidationContext valContext = new ValidationContext(source, null, null);
            var result     = new List<ValidationResult>();
            Validator.TryValidateObject(source, valContext, result, true);
 
            return result;
        } 
 
    }
 
}


Validation Example:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DataAnnotationsLib.Extensions;
using System.ComponentModel.DataAnnotations;
using System.Linq;
 
namespace DataAnnotationsLib.Tests
{
    [TestClass]
    public class UnitTest1
    {rr
        [TestMethod]
        public void TextClass_MaxLenght_ArrayIntProperty_NotValid()
        {
            TestClass textclass = new TestClass();
 
            textclass.ArrayInt = new int[] { 0, 1, 2 };
 
            var errors = textclass.ValidateObject();
 
            Assert.IsTrue(errors.Any());
            Assert.AreEqual(errors.First().ErrorMessage, $"The property {nameof(textclass.ArrayInt)} doesn't have more than 2 elements");
        }
 
 
        [TestMethod]
        public void TextClass_MaxLenght_ArrayIntProperty_Valid()
        {
            TestClass textclass = new TestClass();
 
            textclass.ArrayInt = new int[] { 0, 1};
 
            var errors = textclass.ValidateObject();
 
            Assert.IsFalse(errors?.Any() ?? false);
        }
 
    }
 
    public class TestClass
    {
        public string PropertyOne { get; set; }
        [MaxLength(2, ErrorMessage = "The property {0} doesn't have more than {1} elements")]
        public int[] ArrayInt { get; set; }
 
    }
}


The next article, the part II of DataAnnotations.