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
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:
StringLenghtAttribute
MaxLenghAttribute y MinLenghAttribute
RegularExpressionAttribute
RequieredAttribute
RangeAttribute
CustomValidationAttribute
Atributos Personalizados
Proceso de Validación
Clase Validator
Pasaremos a explicar la ejecución de los métodos:
Método extensor para facilitar el trabajo
IValidatableObject
Conclusión
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:
{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.
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:
StringLenghtAttribute
Marca el tamaño máximo y mínimo que puede tener una cadena link.
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:
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:
RequieredAttribute
Especifica que el campo es un valor obligatorio y no puede
contener un valor null o string.Empty Link.
RangeAttribute
Especifica restricciones de rango de valores para un tipo
específico de datos link.
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.
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:
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.
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:
Como podemos observar tenemos propiedades propias y
constructores personalizados.
En último lugar marcaremos la propiedad como si se tratara
de un atributo de DataAnnotations:
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:
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:
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.
TryValidateProperty(Object, ValidationContext,
ICollection<ValidationResult>)
Este método se encarga de validar una de las propiedades
compuestas por el objeto.
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:
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.
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:
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.
Y su llamada:
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:
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:
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:
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.
Excelente artículo!
ResponderEliminarMuchas gracias,
EliminarEspero que sea el primero de muchos.
Muy bueno. Me ayudo muchísimo gracias.
ResponderEliminar