domingo, 11 de diciembre de 2016

DataAnnotations In Depth II





In this second chapter, we explain all validations forms in DataAnnotations. We will review Validator class with all its structure.
Let's go !!!.













DataAnnotations enables us to work with positive programming, in other words, allows us anticipate exceptions for problems in our business classes or domain classes. It provides better performance, also exists the negative programming version.
  • Positive   àTryValidate.
  • Negative à Validate.

In this Link, there is an information about Exceptions and Performance.

The examples class

The class for all examples, will be a new version of Chapter I class.


public class Customer
{
    [Required(ErrorMessage = "{0} is mandatory")]
    [MaxLength(50, ErrorMessage = "The {0} can not have more than {1} characters")]
    public string Name { get; set; }

    [Range(typeof(DateTime), "01/01/2016", "01/01/2050",
        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; }
}



These are the principal validations classes:

ValidationResult
ValidationResult is a container class validation results. More info Link.
ValidationResult has 2 properties:
  • ErrorMessages .- String readonly property with the information error description.
  • MemberNames.- IEnumerable<string> readonly property with the property name on error.


We have written an extension method to print console the validation errors:

public static string ToDescErrorsString(this IEnumerable<ValidationResult> source, string mensajeColeccionVacia = null)
{
    if (source == null) throw new ArgumentNullException(nameof(source), $"The property {nameof(source)}, doesn't has null value");
 
    StringBuilder resultado = new StringBuilder();
 
    if (source.Count() > 0)
    {
        resultado.AppendLine("There are validation errors:");
        source.ToList()
            .ForEach(
                s =>
                    resultado.AppendFormat("  {0} --> {1}{2}", s.MemberNames.FirstOrDefault(), s.ErrorMessage,
                        Environment.NewLine));
    }
    else
        resultado.AppendLine(mensajeColeccionVacia ?? string.Empty);
 
    return resultado.ToString();
}




Validator class
This is the static helper class. The Validator class allows you execute validation on objects, properties and methods. More information Link.

We can separate its methods in two groups, as we saw earlier:

POSITIVE PROGRAMMING (Return bool value and we have a ICollection<ValidationResult>  parameter)
The return property bool is a validation result. The ICollection<ValidationResult> argument contains details of validations errors. If the validation isn’t errors, this collection will be empty.


                TryValidateObject

public static bool TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult> validationResults);
public static bool TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult> validationResults, bool validateAllProperties);




This method validates all object. TryValidateObject has an overload with a bool argument validateAllProperties, this argument enables in case of validation error continue with the validation all properties.
Arguments:
  • Instance.- The instance of class to validate.
  • ValidationContext.- The context that describes the object to validate.
  • ValidationResults .- The collection of validation results descriptions.
  • ValidateAllProperties.- Enabled continue validate all properties.


Example:

public static void TryValidateObjectExample1()
{
    /// 1.- Create a customer
    var customer = new Customer
    {
        Name                 = string.Empty,
        EntryDate            = DateTime.Today,
        Password             = "AAAA",
        PasswordConfirmation = "BBBB",
        Age                  = -1
    };
    /// 2.- Create a context of validation
    ValidationContext valContext = new ValidationContext(customer, null, null);
    /// 3.- Create a container of results
    var validationsResults = new List<ValidationResult>();
    /// 4.- Validate customer
    bool correct = Validator.TryValidateObject(customer, valContext, validationsResults, true);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Without Errors !!!!"));
    Console.Read();
}


In this example, we have done the second overload with true value (ValidateAllProperties).

Result:










                TryValidateProperty

public static bool TryValidateProperty(object value, ValidationContext validationContext, ICollection<ValidationResult> validationResults);


It has only one overload. It has the same parameters of TryValidateObject, but the first object parameter, does match with one property to validate and the method therefore validates properties.

public static void TryValidatePropertyExample()
{
    /// 1.- Create a customer
    var customer = new Customer
    {
        Name                 = string.Empty,
        EntryDate            = DateTime.Today,
        Password             = "AAAA",
        PasswordConfirmation = "BBBB",
        Age                  = -1
    };
    /// 2.- Create a context of validation
    ValidationContext valContext = new ValidationContext(customer, null, null)
    {
        MemberName = "Age"
    };
    /// 3.- Create a container of results
    var validationsResults = new List<ValidationResult>();
    /// 4.- Validate customer Age Property
    bool correct = Validator.TryValidateProperty(customer.Age, valContext, validationsResults);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Without Errors !!!!"));
    Console.Read();
}


Note:
When we instance the ValidationContext object, we give value to string property MemberName, with the property name to validate.
The TryValidateProperty and the TryValidateObject, are equals, only change the first parameter. In the TryValidateProperty will have to be said the property value.

Result:





TryValidateValue validates a value through ValidationAttribute collection. This is practical to reuse our ValidationAttributes and us to be disabuses of if terms.

public static void TryValidateValueExample()
{
    /// 1.- Create value
    string myPwd = "33223";
    /// 2.- Create ValidationsAttributes
    MinLengthAttribute minLengthAtribute = new MinLengthAttribute(8) { ErrorMessage = "{0} must have {1} caracters minimum" };
    RequiredAttribute requieredAttribute = new RequiredAttribute     { ErrorMessage = "{0} is mandatory" };
    List<ValidationAttribute> atributes  = new List<ValidationAttribute>() { minLengthAtribute, requieredAttribute };
    /// 3.- Create a context of validation
    ValidationContext valContext = new ValidationContext(myPwd, null, null)
    {
        MemberName = "myPwd"
    };
    /// 4.- Create a container of results
    var validationsResults = new List<ValidationResult>();
    /// 5.- Validate myPwd value
    bool correct = Validator.TryValidateValue(myPwd, valContext, validationsResults, atributes);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Without Errors !!!!"));
    Console.Read();
}


Note:
We have created the attributes in code, because we will validate a value and we don’t have properties to mark in the class declaration.

Result:






NEGATIVE PROGRAMMING (throw ValidationException and it hasn’t ICollection<ValidationResult argument )

These methods, remove the word Try in your names. They are void methods. If the validation fail, will be throw a ValidationException. They don’t have a ICollection<ValidationResult> argument, this information will be have inside exception and it isn’t a collection, it is a simple ValidationResult because if validation fail doesn’t verify remaining properties.

public static void ValidateObject(object instance, ValidationContext validationContext);
public static void ValidateObject(object instance, ValidationContext validationContext, bool validateAllProperties);
public static void ValidateProperty(object value, ValidationContext validationContext);
public static void ValidateValue(object value, ValidationContext validationContext, IEnumerable<ValidationAttribute> validationAttributes);


Example:

public static void ValidateObjectExample()
{
    /// 1.- Create a customer
    var customer = new Customer
    {
        Name                 = string.Empty,
        EntryDate            = DateTime.Today,
        Password             = "AAAA",
        PasswordConfirmation = "BBBB",
        Age                  = -1
    };
    /// 2.- Create a context of validation
    ValidationContext valContext = new ValidationContext(customer, null, null);
 
    try
    {
        /// 3.- Validate customer
        Validator.ValidateObject(customer, valContext, true);
    }
    catch (ValidationException ex)
    {
        /// 4.- Print Validations Results
        ValidationResult s = ex.ValidationResult;
 
        Console.WriteLine("There are validation errors:");
        Console.WriteLine("  {0,-20} --> {1}{2}", s.MemberNames.FirstOrDefault(), s.ErrorMessage,
                        Environment.NewLine);
    }
 
            
    Console.Read();
}


Result:





The others methods are the same than ‘Try’ version but with this technique.

Considerations and tests
Remember, we can use the extension method of Chapter I.