Mientras esperamos impacientes la llegada de ASP.NET MVC 2 con su flamante sistema integrado de validación de datos en cliente y servidor, xVal puede sernos de bastante utilidad al ofrecernos prácticamente las mismas funciones previstas para la versión 2, y alguna más 🙂
xVal es un framework para aplicaciones ASP.NET MVC 1.0 (y superiores) creado por Steve Sanderson, y presentado en sociedad el pasado 17 de septiembre, que permite validar la información almacenada en clases del modelo, tanto en el lado cliente como en servidor, de forma automatizada.
Su diseño es muy flexible, permitiéndonos elegir entre distintos marcos de trabajo para la validación en servidor (de serie soporta los atributos DataAnnotations, Castle Validator y NHibernate Validator, aunque se pueden crear proveedores personalizados) y librerías en cliente (inicialmente utiliza el magnífico plugin jQuery Validation).
Vamos a ver, paso a paso, cómo podemos comenzar a utilizarlo de forma inmediata en nuestras aplicaciones ASP.NET MVC 1.0, utilizando las restricciones DataAnnotations y la librería de validación para jQuery citada anteriormente.
1. Preparamos la infraestructura
En primer lugar, vamos a preparar el entorno para utilizar el framework de validación:
- Descargamos xVal desde CodePlex. Se distribuye en un fichero .zip que incluye tanto el ensamblado como archivos de soporte, así que lo descomprimimos en cualquier parte para tener los archivos a mano.
- Descargamos el plugin de validación de jQuery, por ejemplo, desde el CDN de Microsoft (¿qué es el Microsoft Ajax CDN?) y lo incluimos en la carpeta
/scripts
de nuestro proyecto. Otra posibilidad sería no descargarlo y referenciarlo directamente nuestras páginas, a gusto del consumidor 😉 - Añadimos a la carpeta
/scripts
del proyecto la libreríaxVal.jQuery.Validate.js
presente en el raíz del comprimido de xVal. Este archivo, junto con el anterior, son imprescindibles para montar la validación automática en el lado cliente que veremos más adelante. Además, para que los mensajes aparezcan en nuestro idioma, copiamos también al mismo sitio el archivoxVal.Messages.es-ES.js
, disponible en la carpeta “internationalization” del zip. - Copiamos al directorio
/bin
de nuestra aplicación el ensambladoxVal.dll
, o simplemente añadimos una referencia en el proyecto hacia dicho archivo, que encontraréis en el raíz del fichero comprimido que hemos descargado en primer lugar. - Retocamos ligeramente el Web.config de nuestro proyecto para que sea incluido automáticamente el espacio de nombres
xVal.Html
en nuestras vistas, facilitando así el acceso a los helpers suministrados:<system.web>
<pages>
<namespaces>
<!-- Añadir la siguiente línea -->
<add namespace="xVal.Html"/>
</namespaces>
</pages>
</system.web>
Los pasos comentados hasta el momento son genéricos, es decir, tendremos que realizarlos en todos los proyectos en los que vayamos a utilizar este framework de validación. Podría ser interesante, por tanto, crearnos una plantilla de proyecto en la que tengamos ya todo este trabajo realizado.
2. Definimos las restricciones con Data Annotations
Ahora vamos a definir, de forma declarativa, las restricciones que queremos imponer a las propiedades de las clases del modelo, utilizando los atributos denominado data annotations, distribuidos con .NET 3.5 y originalmente diseñados para trabajar con la tecnología Dynamic Data.
- Dado que vamos a declarar las restricciones utilizando los atributos data annotations, hay que añadir referencia en el proyecto a
System.ComponentModel.DataAnnotations.
- A continuación, tenemos que incluir en el proyecto un método que nos permita recorrer las anotaciones de las clases, ejecutarlas e ir generando los errores apropiadamente. Este método lo utilizaremos más adelante, desde los componentes del Modelo, para comprobar si los valores presentes en las propiedades cumplen los requisitos impuestos por el dominio del sistema.
El código es muy sencillo, el propio Sanderson nos facilita uno en el proyecto de demostración de xVal, que podéis copiar y pegar:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using xVal.ServerSide;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
namespace MyProject.Validation
{
public static class DataAnnotationsValidationRunner
{
public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
var metadataAttrib = instance.GetType()
.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
.OfType<MetadataTypeAttribute>().FirstOrDefault();
var buddyClassOrModelClass = metadataAttrib != null ?
metadataAttrib.MetadataClassType:
instance.GetType();
var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass)
.Cast<PropertyDescriptor>();
var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType())
.Cast<PropertyDescriptor>();
return from buddyProp in buddyClassProperties
join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name
from attribute in buddyProp.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(modelProp.GetValue(instance))
select new ErrorInfo(
buddyProp.Name,
attribute.FormatErrorMessage(string.Empty), instance);
}
}
}
Ojo, esto sólo tenemos que hacerlo cuando estemos utilizando DataAnnotations, como es el caso; si utilizamos otros frameworks como el de Castle o NHibernate no será necesario, puesto que incluyen ya implementaciones para realizar esta tarea.
- Marcamos las propiedades de la entidad del modelo con las restricciones a aplicar durante la validación. Podemos utilizar los atributos disponibles para restringir los valores permitidos en cada propiedad como
Required
,StringLength
,Range
y otros (puedes ver la relación completa aquí).Un ejemplo podría ser el siguiente, en el que vemos varias restricciones aplicadas a las propiedades de la entidad:
public class Recluta
{
[Required, StringLength(30)]
public string Nombre { get; set; }
[Required, StringLength(40)]
public string Apellidos { get; set; }
[Required, Range(1, 4)]
public int Talla { get; set; }
[Required, DataType(DataType.Date)]
public DateTime FechaNacimiento { get; set; }
}
Sin embargo, muchos os preguntaréis qué ocurre cuando estas entidades de datos las generamos con herramientas automáticas, como el diseñador de Entity Framework. En estos casos, cada vez que generemos el modelo, las clases serían machacadas por las nuevas versiones, y nuestras anotaciones pasarían a mejor vida.Afortunadamente, existe la posibilidad de declarar una clase paralela (o, como la llaman, ‘buddy class’), en la que definiremos exclusivamente las propiedades a las que queremos añadir alguna anotación. Además, marcaremos la entidad original (aprovechando que la mayoría de estas herramientas automáticas las generan como parciales) indicando la clase que contiene los metadatos asociados a ésta:
// Indicamos en la entidad original que los
// metadatos se encuentran en 'ReclutaMedata'
[MetadataType(typeof(ReclutaMetadata))]
public partial class Recluta
{
// Nada más que añadir aquí
}
// Definimos los metadatos para la entidad 'Recluta'
public class ReclutaMetadata
{
[Required, StringLength(30)]
public string Nombre { get; set; }
[Required, StringLength(40)]
public string Apellidos { get; set; }
[Required, Range(1, 4)]
public int Talla { get; set; }
[Required, DataType(DataType.Date)]
public DateTime FechaNacimiento { get; set; }
}
Como podéis observar, la entidad y la clase de metadatos son prácticamente iguales. Un poco anti-DRY sí que es, pero bueno.
3. Validamos en servidor
La propuesta de xVal para el lado del servidor consiste en desplazar la lógica de validación al modelo, dado que es éste el que normalmente impone las restricciones en los datos que han de estar presentes en las entidades del dominio. Una forma de realizarlo sería así:
- por cada entidad, creamos un método que valide la información del mismo, primero invocando al ValidationRunner (el proceso de validación basado en anotaciones descrito anteriormente), y luego añadiendo aquellas reglas de negocio no incluidas en dichas anotaciones). Un ejemplo podría ser el siguiente:
private void validar(Recluta recluta)
{
var errors = DataAnnotationsValidationRunner.GetErrors(recluta);
if (errors.Any())
throw new RulesException(errors);
// Regla de negocio adicional: prohibido alistarse reclutas
// con hermanos en el cuartel...
if (tieneHermanos(recluta.Apellidos))
{
throw new RulesException("Apellidos",
"El recluta tiene hermanos ya alistados", recluta);
}
}
Este método podría incluirse en la propia entidadRecluta
, o bien donde se implemente la lógica de negocio asociada a la misma.En cualquier caso, como se puede observar, cuando se producen errores debido a un incumplimiento de las restricciones indicadas en las anotaciones, se lanza una excepción de tipoRulesException
(facilitada por xVal) que contiene una descripción de los problemas encontrados. Asimismo, la existencia de otro tipo de problemas, hallados de forma programática, son lanzados también en una excepción del mismo tipo. - antes de realizar operaciones susceptibles de modificar el estado del sistema, tenemos que asegurarnos de que las entidades son válidas. Observad, por ejemplo, la implementación del método que nos permite añadir un recluta al sistema:
public void Crear(Recluta recluta)
{
validar(recluta);
reclutas.Add(recluta);
}
Si se producen errores en la validación, el método
Crear()
será interrumpido por la excepciónRulesException
lanzada desdevalidar()
, y llegará finalmente hasta el Controlador, donde deberá ser procesada. - desde el punto de vista del Controlador, podremos comprobar que el patrón a seguir es realmente sencillo, pues toda la lógica de validación la hemos desplazado al modelo.
El siguiente código muestra los métodos de acción asociados a la creación de una entidad; el primero de ellos simplemente se encarga de mostrar la vista de edición sobre un objeto recién creado, mientras que el segundo obtiene los datos del recluta desde el formulario, e intenta realizar la operación de creación sobre el modelo:
public ActionResult Create()
{
return View(new Recluta());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Recluta recluta)
{
try
{
gestorDeReclutas.Crear(recluta);
}
catch (RulesException ex)
{
ex.AddModelStateErrors(this.ModelState, "");
}
if (!ModelState.IsValid)
return View(recluta);
else
return RedirectToAction("Index");
}
Como se puede observar, se capturan las excepciones de validación que se produzcan al realizar el alta, añadiendo alModelState
los errores que se estén informando en éstas y, en función de la validez de los datos, enviando al usuario a la vista correspondiente.
4. Validamos en el cliente
Está claro que la validación en el lado del servidor es la que única que debemos hacer obligatoriamente; el lado cliente es sensible al equipo del usuario, que puede estar accediendo desde dispositivos sin javascript (o con esta característica desactivada), o incluso puede ser fácilmente manipulado, por lo que no podemos confiar en que los datos lleguen ya siendo válidos.
Sin embargo, ofrecer validación en cliente es una característica imprescindible hoy en día vistas a realizar interfaces muy usables, capaces de ofrecer al usuario feedback inmediato sobre sus acciones.
Con xVal, añadir en este punto validaciones en cliente es realmente sencillo, pues ya tenemos realizado prácticamente todo el trabajo, quedando simplemente:
- incluir las librerías script en las vistas donde vayamos a realizar la edición de las entidades del modelo. Si vamos a utilizarlas en muchos sitios, lo más fácil es añadirlas a la página maestra:
<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="../../Scripts/xVal.jquery.validate.js" type="text/javascript"></script>
<script src="../../Scripts/xVal.Messages.es-ES.js" type="text/javascript"></script>
- generar en la vista los scripts de validación de las entidades que nos interesen. En el siguiente código generamos la lógica para la entidad
Recluta
:
<%= Html.ClientSideValidation<MiProyecto.Models.Recluta>() %>
5. ¡Y hasta aquí hemos llegado!
A lo largo de este post hemos recorrido los pasos necesarios para echar a andar el framework xVal en una aplicación ASP.NET MVC 1.0.
Faltan muchos aspectos por comentar, como la posibilidad de utilizar validaciones Ajax en servidor, permitir la localización en decimales, escribir nuestros propios atributos de anotaciones, etc., pero creo que este post ya es lo suficientemente extenso… ya lo veremos en otra ocasión.
Puedes descargar el proyecto de demostración desde Skydrive:
Publicado en: Variable not found.
Genial las explicaciones!
A ver si encuentro un hueco para jugar un poco con ello.
Excelente explicación. Muchas gracias, muy útil.