lunes, 21 de octubre de 2013

Validaciones con DataAnnotations


El espacio de nombres System.ComponentModel.DataAnnotations, nos proporciona una serie de clases, atributos y métodos para realizar validación dentro de nuestros programas en .NET.

 En ocasiones, debido a que tecnologías como WPF, Silverlight, ASP MVC, Entity Framework, etc., realizan comprobaciones de validación automáticas con clases marcadas con estos atributos, pensamos que es exclusivo de éstas, pero no tiene nada que ver, está al alcance de cualquier clase del Framework como veremos más adelante.



Añadiendo restricciones a nuestras clases de negocio

La forma que tiene DataAnnotations de restringir las propiedades de nuestras clases es mediante atributos como se muestra en nuestro ejemplo:

class Usuario
{
    [Required]
    public string   NombreUsuario        { get; set; }
    public DateTime FechaEntrada         { get; set; }
    public string   Password             { get; set; }
    public string   PasswordConfirmacion { get; set; }
}

En nuestro ejemplo hemos utilizado el atributo Requiered, pero hay algunos más como veremos a continuación.


Tipos de atributos de validación disponibles

Todos los atributos que vamos a ver en esta sección heredan de la clase abstracta ValidationAttribute, de la cual podemos encontrar más información en el siguiente Link.

La validación de ValidationAttribute, valida propiedades de forma individual.

ErrorMessage, es una propiedad de esta clase que heredaran todos nuestros atributos de validación y que es importante señalar ya que en ella podremos customizar el mensaje que arrojará la validación cuando esta propiedad no pase la misma.

Esta propiedad tiene un ‘FormatString’ implícito por el cual mediante la notación estándar de ‘FormatString’ podemos referirnos primero al nombre de la propiedad y después a los respectivos parámetros según el tipo de atributo:

[MaxLength(2, 
     ErrorMessage="La propiedad {0} no puede tener más de {1} elementos")]
public int[] EjArrayInt { get; set; }






{0} à Nombre de la propiedad
{1} à Parámetro 1
{2} à Parámetro 2
{n} à Parámetro n

Este ejemplo de formato es el implementado por el atributo MaxLenght, por lo que no tiene que decir que la posición y número de parámetros sea igual para todos, tendremos que investigar cada uno de ellos antes de utilizarlo.


CompareAttribute

Es un atributo que comprara dos propiedades Link.


class Usuario
{
    public string NombreUsuario { get; set; }
    public DateTime FechaEntrada { get; set; }
    public string Password { get; set; }
    [Compare("Usuario.Password"
        ErrorMessage = "La clave y la verificación del pwd no son correctas")]
    public string PasswordConfirmacion { get; set; }
}

Este atributo compara la propiedad marcada, con la propiedad de la misma clase indicada por su nombre a través de un parámetro string en su constructor.


DataTypeAttribute

Este atributo permite realizar un marcado para una propiedad/campo, de una forma más específica que la que te otorgan los tipos de datos de .NET Framework. Link.

En aplicaciones que hacen uso de plantillas (ASP, Silverlight, etc) pueden ser utilizados para modificar la forma de mostrar sus datos. En nuestro caso si nuestra propiedad estuviera vinculada a una caja de texto está podría proteger el texto (con caracteres “*” por ejemplo) de manera automática, solo por la lectura de este atributo.

Los valores del enum DataType son los siguientes:


        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

Marca el tamaño máximo y mínimo que puede tener una cadena link.

class Usuario
{
    [StringLength(50, MinimumLength = 10, 
        ErrorMessage="La propiedad {0} debe tener {1} caracteres de máximo y {2} de mínimo")]
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
    public string Mail { get; set; }
}



MaxLenghAttribute y MinLenghAttribute

Estos atributos fueron añadidos para la versión de Entity Framework 4.1.

Especifican el tamaño máximo y mínimo de elementos de una propiedad de tipo array. Hay que destacar que las clases string son tomadas a semejanza de un char[] (MaxLink, MinLink):


En el ejemplo podemos ver los 2 tipos, tanto como para el tipo string como para el tipo array:

class Usuario
{
    [MaxLength(50, ErrorMessage = "El {0} no puede superar los {1} caracteres")]
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
    [MaxLength(2, ErrorMessage="La propiedad {0} no puede tener más de {1} elementos")]
    public int[] EjArrayInt { get; set; }
}

En el caso de las propiedades de tipo array, deberán de ser eso tipo array, no valiendo ningún tipo colección (IEnumerable<T>).


Es importante señalar que MaxLenghtAttribute y MinLengthAttribute son utilizados por EF para la validación en el lado del servidor y estos se diferencian del StringLengthAttribute, que utilizan un atributo para cada validación, y no solo sirven para validar tamaños de string, sino que también validan tamaños de arrays.


RegularExpressionAttribute

Especifica una restricción mediante una expresión regular Link:

class Usuario
{
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
    [RegularExpression("\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\b", ErrorMessage="Mail incorrecto")]
    public string Mail { get; set; }
}


RequieredAttribute

Especifica que el campo es un valor obligatorio y no puede contener un valor null o string.Empty Link.

class Usuario
{
    [Required(ErrorMessage = "{0} es un campo obligatorio")]
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
    public string Mail { get; set; }
}


RangeAttribute

Especifica restricciones de rango de valores para un tipo específico de datos link.

class Usuario
{
    public string NombreUsuario { get; set; }
    [Range(typeof(DateTime), "01/01/1900", "01/01/2014",
        ErrorMessage = "fechas aceptadas para campo {0} entre {1} y {2}")]
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
    [Range(0, 150, ErrorMessage = "Debe tener una edad entre 0 y 150 años")]
    public int Edad { get; set; }
}

Como se puede comprobar cuando se aplica a valores numéricos no hay que realizar ningún tipo de indicación del tipo sobre el que se realizará la incorporación de intervalo de datos. Cuando se hace sobre tipos más específicos es imprescindible indicar el tipo mediante el primer parámetro.

Si no indicáramos el tipo de datos del rango, este realizará un casting interno a un tipo numérico.

[Range(8, 10, ErrorMessage = "{0} INCORRECTO !!!")]
public string NombreUsuario { get; set; }

En este ejemplo, si indicáramos un nombre de “2”, no pasaría la validación ya que haría el casting no estaría dentro del intervalo de 8 a 10. En caso de indicar un nombre de “9”, pasaría la validación.


CustomValidationAttribute

Especifica un método de validación personalizado link.

Para poder utilizar un CustomValidationAttribute, tendremos que reutilizar o desarrollar de cero una clase que tenga un método estático que devuelva un ValidationResult (clase dentro del espacio de nombres System.ComponentModel.DataAnnotations) y que reciba un parámetro del mismo tipo que la propiedad a validar:

public class UsuarioDateTimeValidation
{
    public static ValidationResult ValidaFechaFestivo(DateTime fecha)
    {
        return fecha.DayOfWeek == DayOfWeek.Saturday || fecha.DayOfWeek == DayOfWeek.Sunday
            ? new ValidationResult("Los festivos no son aceptados para esta fecha.")
            : ValidationResult.Success;
    }
}

Después marcaremos la propiedad a validar y en el constructor del CustomValidationAttribute indicaremos el tipo de la clase de validación y como segundo parámetro una propiedad string con el nombre del método que realizará la validación.

class Usuario
{
    public string NombreUsuario { get; set; }
    [CustomValidation(typeof(UsuarioDateTimeValidation), "ValidaFechaFestivo")]
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
}


Atributos Personalizados

Esta última opción, no se trata de un atributo disponible dentro del espacio de nombres DataAnnotations, sino de la posibilidad de construirnos nuestro propio atributo.

Tenemos la opción de poder crear nuestros propios constructores, propiedades y usar prácticamente todas las bondades que nos ofrece el framework.

Lo primero que haremos será una clase que herede de ValidationAttribute y sobrescribiremos el método IsValid:

public class ControlDateTimeAttribute : ValidationAttribute
{
    private DayOfWeek[] DiasNoPermitidos;
    private bool        LanzarExcepcion;
 
    public ControlDateTimeAttribute(params DayOfWeek[] diasNoPermitidos)
    {
        this.LanzarExcepcion  = false;
        this.DiasNoPermitidos = diasNoPermitidos;
    }
 
    public ControlDateTimeAttribute(bool lanzarExcepcion, params DayOfWeek[] diasNoPermitidos)
    {
        this.LanzarExcepcion  = lanzarExcepcion;
        this.DiasNoPermitidos = diasNoPermitidos;
    }
 
 
    public override bool IsValid(object value)
    {
        DateTime fecha;
 
        if (!DateTime.TryParse(value.ToString(), out fecha))
        {
            if (this.LanzarExcepcion)
                throw new ArgumentException(
                    "El atributo ControlDateTimeAttribute, solo permite validar propiedades de tipo DateTime o Parseables a DateTime");
            else
                return false;
        }
 
        return this.DiasNoPermitidos.Contains(fecha.DayOfWeek);
    }
}

Como podemos observar tenemos propiedades propias y constructores personalizados.

En último lugar marcaremos la propiedad como si se tratara de un atributo de DataAnnotations:

class Usuario
{
    public string NombreUsuario { get; set; }
    [ControlDateTimeAttribute(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,
        ErrorMessage = "La {0} no es aceptada")]
    public DateTime? FechaEntrada { get; set; }
    public string Password { get; set; }
    public string PasswordConfirmacion { get; set; }
}





Proceso de Validación 

En esta fase, presentaremos las diferentes opciones de realizar la validación de los objetos que crearemos a partir de los ejemplos anteriores.

Utilizaremos esta implementación de la clase usuario para realizar los ejemplos de validación:

class Usuario
{
    [Required(AllowEmptyStrings = false, ErrorMessage = "El campo {0} es obligatorio")]
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    [MinLength(8, ErrorMessage = "{0} Tiene que tener mínimo {1} caracteres")]
    public string Password { get; set; }
    [Compare("Password", ErrorMessage = "La clave y la verificación del pwd no son correctas")]
    [MinLength(8, ErrorMessage = "{0} Tiene que tener mínimo {1} caracteres")]
    public string PasswordConfirmacion { get; set; }
}



Clase Validator

Es una clase estática auxiliar que nos permitirá realizar labores de validación para objetos, propiedades y métodos link.

Esta clase posee una serie de métodos separados en 2 bloques.

1.- Validación con programación en positivo, todos devuelven un bool con el resultado de la validación:
  • ·         TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult> validationResults, bool validateAllProperties)
  • ·         TryValidateProperty(object value, ValidationContext validationContext, ICollection<ValidationResult> validationResults)
  • ·         TryValidateValue(object value, ValidationContext validationContext, ICollection<ValidationResult> validationResults, IEnumerable<ValidationAttribute> validationsAttributes)

2.- Validación con programación en negativo, son de tipo void y lanzarán una excepción en caso de no pasar la validación:
  • ·         ValidateObject(object instance, ValidationContext validationContext, bool validateAllProperties)
  • ·         ValidateProperty(object value, ValidationContext validationContext)
  • ·         ValidateValue(object value, ValidationContext validationContext, IEnumerable<ValidationAttribute> validationsAttributes)

Para todos estos métodos, se comparte una serie de parámetros que explicamos a continuación:

  • o   object instance/value .- Es el objeto, propiedad o valor sobre el que se realizará la validación.
  • o   ValidationContext validationContext .- Es la clase encargada de definir el contexto sobre el que se realiza la validación Link.
  • o   ICollection<ValidationResult> validationResults .- Es la colección donde se almacenan los detalles del resultado de la validación. Solo para los métodos ‘Try’ (prog. Positiva) para los de programación en negativo, esta información estará contenida en la excepción de tipo ValidationException que contendrá una propiedad de tipo ValidationResult.
  • o   bool validateAllProperties.- Booleano que habilita la comprobación de todas propiedades del objeto.
  • o   IEnumerable<ValidationAttribute> validationsAttributes .- Collección de atributos de validación que se aplicarán al valor a validar.
Otra de las clases a tener en cuenta es la clase ValidationResult. Esta clase almacena la información obtenida como resultado de un proceso de validación. Link.

Contiene 2 propiedades:
  • o   ErrorMessage .- Propiedad readonly de tipo string que indica la descripción del error de validación.
  • o   MemberNames .- Propiedad IEnumerable<string> que indica los miembros que contienen el error de validación.
Para hacer la parte de comprobación más amena e intentar ensuciar menos el código de ejemplo, vamos a añadir un campo extensor de IEnumerable<string> que realice el pintado por consola de los resultados de validación:

public static class Extensiones
{
    public static string ToDescErrorsString(this IEnumerable<ValidationResult> source, string mensajeColeccionVacia = null)
    {
        if (source == null)
            throw new AggregateException("El grupo de validaciones no puede ser nulo");
 
        StringBuilder resultado = new StringBuilder();
 
        if (source.Count() > 0)
        {
            resultado.AppendLine("Se han encontrado los siguientes erroes de validación:");
            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();
    }
}


Pasaremos a explicar la ejecución de los métodos:



TryValidateObject(Object, ValidationContext, ICollection<ValidationResult>)
TryValidateObject(Object, ValidationContext, ICollection<ValidationResult>, Bool)

Este método se encarga de validar un objeto por completo. Como se puede apreciar contiene una sobrecarga que indica si fuerza a comprobar todas y cada una de sus propiedades.

static void Main(string[] args)
{
    Usuario usuario = new Usuario { NombreUsuario = string.Empty, FechaEntrada = DateTime.Today, Password = "1111", PasswordConfirmacion = "222" };
 
    ValidationContext valContext = new ValidationContext(usuario, null, null);
    var validationsResults = new List<ValidationResult>();
    bool correcto = Validator.TryValidateObject(usuario, valContext, validationsResults, true);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Sin errores"));
    Console.Read();
}
















TryValidateProperty(Object, ValidationContext, ICollection<ValidationResult>)

Este método se encarga de validar una de las propiedades compuestas por el objeto.

static void Main(string[] args)
{
    Usuario usuario = new Usuario { NombreUsuario = string.Empty, FechaEntrada = DateTime.Today, Password = "1111", PasswordConfirmacion = "222" };
 
    ValidationContext valContext = new ValidationContext(usuario, null, null) { MemberName = "Password" };
    var validationsResults = new List<ValidationResult>();
    bool correcto = Validator.TryValidateProperty(usuario.Password, valContext, validationsResults);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Sin errores"));
    Console.Read();
}











A resaltar:
  • o   Es obligatorio en la creación del objeto ValidationContext, configurar la propiedad MemberName con un string con el nombre de la propiedad.
  • o   El único cambio en la llamada respecto a TryValidateObject, estará en el primer parámetro donde indicaremos la propiedad a validar
  • o   En el ejemplo hemos hardcodeado la propiedad “Password”, pero podemos consultar dinámicamente las propiedades de un objeto mediante el espacio de nombres System.Reflection de la siguiente forma:
Usuario usuario = new Usuario { NombreUsuario = string.Empty, FechaEntrada = DateTime.Today };
 
foreach (var propertyInfo in usuario.GetType().GetProperties())  //typeof(Usuario).GetProperties()
{
    string nombrePropiedad = propertyInfo.Name;
}

Podemos consultarlas desde la instancia o desde el tipo directamente.



TryValidateValue(Object, ValidationContext, ICollection<ValidationResult>, IEnumerable<ValidationAttribute>)

Este método se encarga de realizar la validación de un valor mediante un grupo de atributos de validación.

static void Main(string[] args)
{
    string miPassword = "33223";
 
    MinLengthAttribute minLengthAtribute = new MinLengthAttribute(8) 
        { ErrorMessage = "{0} Tiene que tener mínimo {1} caracteres" };
    RequiredAttribute requieredAttribute = new RequiredAttribute();
    List<ValidationAttribute> atributos = new List<ValidationAttribute>(){ minLengthAtribute, requieredAttribute };
 
    var validationsResults = new List<ValidationResult>();
    ValidationContext valContext = new ValidationContext(miPassword, null, null) {MemberName = "miPassword"};
    bool correcto = Validator.TryValidateValue(miPassword, valContext, validationsResults, atributos);
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Sin errores"));
    Console.Read();
}












ValidateObject(Object, ValidationContext, ICollection<ValidationResult>)
ValidateObject(Object, ValidationContext, ICollection<ValidationResult>, Bool)
ValidateProperty(Object, ValidationContext, ICollection<ValidationResult>)
ValidateValue(Object, ValidationContext, ICollection<ValidationResult>, IEnumerable<ValidationAttribute>)

Todos estos métodos funcionan de manera muy similar a los expuestos anteriormente en sus homónimos ‘Try’, con la diferencia que estos no devuelven ningún bool con el resultado de la validación, ya que estos lanzan un ValidationException en caso de validación errónea.


Code:
static void Main(string[] args)
{
 
    try
    {
        Usuario usuario = new Usuario { NombreUsuario = string.Empty, FechaEntrada = DateTime.Today, Password = "1111", PasswordConfirmacion = "222" };
 
        ValidationContext valContext = new ValidationContext(usuario, null, null);
        Validator.ValidateObject(usuario, valContext, true);
 
        Console.Read();
    }
    catch (ValidationException valException)
    {
        ValidationResult vr = valException.ValidationResult;
        Console.WriteLine("Se han encontrado los siguientes erroes de validación:");
        Console.WriteLine("  {0} --> {1}{2}", vr.MemberNames.FirstOrDefault(), vr.ErrorMessage,Environment.NewLine);
        Console.Read();
    }
}



Método extensor para facilitar el trabajo

Ya que prácticamente la definición de los contexto de validación mediante el objeto ValidationContext no es muy normal indicar ni un contenedor de propiedades específico, ni un proveedor de servicios, ya que en la construcción del objeto el segundo y tercer parámetro siempre suelen ir a null, construiremos unos métodos extensores para realizar la validación que nos ahorrarán mucho trabajo.

public static IEnumerable<ValidationResult> ValidateObject(this object source)
{
    ValidationContext valContext = new ValidationContext(source, null, null);
    var resultado     = new List<ValidationResult>();
    Validator.TryValidateObject(source, valContext, resultado, true);
 
    return resultado;
} 

Y su llamada:

static void Main(string[] args)
{
 
    Usuario usuario = new Usuario { NombreUsuario = string.Empty, FechaEntrada = DateTime.Today, Password = "1111", PasswordConfirmacion = "222" };
 
    var validationsResults = usuario.ValidateObject();
 
    Console.WriteLine(validationsResults.ToDescErrorsString("Sin errores"));
    Console.Read();
}

Con el mismo resultado de salida.


Igual que con este, podríamos realizar otro método para los otros tipos de validación.


IValidatableObject

Interfaz que permite realizar una validación completa de un objeto Link.

A diferencia de los atributos que solo validaban una única propiedad con IValidatableObject, podemos realizar la validación de un conjunto de propiedades.

Esta interfaz se compone de un único método, Validate:


IEnumerable<ValidationResult> Validate(ValidationContext)


Tomaremos como ejemplo, la clase Usuario del paso anterior que ya contiene algún atributo de validación. Le hemos añadido dos propiedades más para dar significado a este ejemplo, que son TipoValidación del tipo de datos enum TipoValidación y un DateTime FechaNacimiento:

class Usuario : IValidatableObject
{
    [Required(AllowEmptyStrings = false, ErrorMessage = "El campo {0} es obligatorio")]
    public string NombreUsuario { get; set; }
    public DateTime? FechaEntrada { get; set; }
    [MinLength(8, ErrorMessage = "{0} Tiene que tener mínimo {1} caracteres")]
    public string Password { get; set; }
    [Compare("Password", ErrorMessage = "La clave y la verificación del pwd no son correctas")]
    [MinLength(8, ErrorMessage = "{0} Tiene que tener mínimo {1} caracteres")]
    public string PasswordConfirmacion { get; set; }
 
    public TipoContenido TipoContenido { get; set; }  
    public DateTime FechaNacimiento { get; set; }
 
    #region IValidatableObject Members
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(TipoContenido == TipoContenido.Adultos && this.FechaNacimiento.AddYears(18) > DateTime.Today)
            yield return new ValidationResult("No puede tener acceso a contenido de adultos si no es mayor de 18 años", new [] { "TipoContenidos", "FechaNacimiento" });
 
        /// if ... 
        /// {
        ///     yield return new ValidationResult( ... )
        /// } 
        ///  seguiríamos añadiendo más comprovaciones
    }
 
    #endregion
}
 
 
public enum TipoContenido
{
    Deportes,
    Lectura,
    Adultos
}

Como se puede ver con este tipo de validación estamos haciendo una validación compuesta de los atributos añadidos, donde comprobamos si el usuario es mayor de edad para poder ver contenido de adultos.

Nota:

Cuando utilizamos tecnologías con validación automática implementada como MVC, Entity Framework o WPF, en el momento de realizar la llamada a la validación del objeto, primero realizan la validación de los atributos, y si encuentran algún error en éstos, informan de inmediato con los resultados, pero no llegan a llamar al método Validate, este método solo es llamado en caso de que se supere toda la validación de las propiedades por separado mediante sus atributos.

Nosotros para poder realizar la comprobación del objeto completo, deberíamos llamar primero a la parte de validación de los atributos con la clase Validator, y después a su método Validate. Ya que realizar dos llamadas no es el trabajo más óptimo para un desarrollador, a no ser que quiera hacer así por alguna causa específica, se suele hacer que el objeto (tenga o no tenga necesidad de hacer una validación compuesta) implemente el IValidatableObject, y este método se hace la llamada Validator.

Ejemplo de Validación compuesta + Validator:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if(TipoContenido == TipoContenido.Adultos && this.FechaNacimiento.AddYears(18) > DateTime.Today)
        yield return new
            ValidationResult("No puede tener acceso a contenido de adultos si no es mayor de 18 años", 
            new [] { "TipoContenidos", "FechaNacimiento" });
 
    /// if ... 
    /// {
    ///     yield return new ValidationResult( ... )
    /// } 
    ///  seguiríamos añadiendo más comprovaciones
            
    var validationsResults = new List<ValidationResult>();
 
    if (!Validator.TryValidateObject(this, validationContext, validationsResults, true))
        foreach (var validationResult in validationsResults)
            yield return new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames);
}


Por supuesto podemos cambiar el orden de las llamadas si queremos que ser realice la validación de forma inversa.

Ejemplo de solo llamada a Validator:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    var validationsResults = new List<ValidationResult>();
 
    Validator.TryValidateObject(this, validationContext, validationsResults, true);
 
    return validationsResults;
}



Conclusión

DataAnnotations es la opción de validación más compatible dentro del framework, utilizando este tipo de validación nos aseguramos que nuestras clases serán compatibles con procesos automáticos de validación de prácticamente todas las tecnologías en .Net. Hay otras alternativas como IDataErrorInfo, INotifyDataErrorInfo, ValidationRules, validación en los setters, etc., pero estas técnicas o están muy ligadas a la tecnología o al UI o utilizan programación en negativo, siendo más complicado de utilizar y penalizando en el rendimiento de nuestros procesos.


DataAnnotations nos permite centralizar el proceso de validación, haciendo realmente sencillo su uso para el resto de consumidores, contando que en casos de uso anteriormente nombrados (WPF, Silverlight, Entity Framework, MVC) su uso es automático.