Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
lunes, 26 de octubre de 2009

Dicen las malas lenguas ;-) que durante una reunión del equipo de diseño de ASP.NET MVC alguien dijo: “necesitaremos un control tipo Repeater”, refiriéndose a algún tipo de mecanismo para mostrar datos tabulados de forma sencilla. Y la respuesta del jefe técnico fue, “ya lo tenemos: se llama bucle foreach”.

Anécdotas aparte, es cierto que en ASP.NET MVC 1.0 no existe otro mecanismo que el bucle de toda la vida para mostrar datos en tablas, la típica rejilla con información sobre elementos de una colección tan habitual en nuestras aplicaciones. La máxima ayuda que tenemos “de serie” es el andamiaje generado por Visual Studio a la hora de generar una vista sobre una enumeración de elementos, que es válida únicamente en escenarios muy sencillos.

Y aquí es donde entra jQuery Grid Plugin (jqGrid para los amigos), un plugin para jQuery que se integra perfectamente con ASP.NET MVC y que permite simplificar enormemente el desarrollo  de vistas de tipo grid ofreciendo al mismo tiempo unas funcionalidades espectaculares: paginación, carga de datos de forma asíncrona vía Ajax, ordenación y redimensionado de columnas, edición de celdas, interfaz multi-idioma basado en temas, y un larguísimo etcétera.

En este post vamos a implementar un grid simple de consulta de datos utilizando jqGrid 3.5 y ASP.NET MVC 1.0, paso a paso. Además, al final del post encontraréis un enlace para descargar el proyecto de demostración para Visual Studio 2008 Express.

1. Descargamos jqGrid

Descarga de jqGridLo primero, como siempre, es descargar los componentes necesarios. Para hacernos con jqGrid, es necesario visitar la página de descargas del proyecto, en la que podemos encontrar un selector que nos permitirá elegir los módulos asociados a las funcionalidades que vayamos a utilizar.

En nuestro caso, dado que vamos a implementar un escenario muy simple, seleccionaremos únicamente el módulo “Grid base” y pulsamos el botón download situado en el pie de la tabla.

La descarga es un archivo .zip bastante ligerito cuyo contenido utilizamos así:

  • jquery.jqGrid.min.js, que encontraréis en la carpeta “js” del archivo descargado, lo copiamos a la carpeta “scripts” del proyecto ASP.NET MVC.
  • grid.locale-sp.js , el archivo de localización al español que encontraremos en la carpeta “js/i18n” del .zip, lo copiamos también a la carpeta “scripts” del proyecto.
  • ui.jqgrid.css , disponible en la carpeta “css” del archivo comprimido, lo pasamos a la carpeta “content” del proyecto.

Archivos del proyecto 2. Descargamos un tema visual

jqGrid utiliza los temas visuales de jQuery UI para componer el interfaz, por lo que es necesario descargar alguno de los disponibles o bien crear uno personalizado con la herramienta on-line disponible en su página. Para nuestro ejemplo, vamos a utilizar el tema “Cupertino”.

Como en el caso anterior, seleccionamos los componentes a obtener (en principio, basta con UI Core a no ser que vayáis a utilizar esta librería para otras cosas), el tema, y pulsamos el botón download. Descargaremos un archivo .zip en cuyo  interior encontramos, dentro de la carpeta “css”, una subcarpeta con el nombre del tema elegido y que copiamos a la carpeta “content” del proyecto MVC.

La imagen anterior muestra la distribución de los archivos descritos hasta el momento en las distintas carpetas en las que hay que colocarlos.

3. Incluimos los componentes en el proyecto ASP.NET MVC

Tras crear el proyecto ASP.NET MVC, vamos a incluirle ahora las referencias a librerías y archivos necesarios para poder utilizar jqGrid.

Básicamente, tendremos que incluir en las páginas donde vayamos a utilizarlo referencias a jQuery, jqGrid, el archivo de localización, la hoja de estilos utilizada por jqGrid, y el tema que estemos empleando. Lo más fácil es hacerlo a nivel de página maestra, por ejemplo así:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
 
    <link href="../../Content/ui.jqgrid.css" rel="stylesheet" type="text/css" />
    <link href="../../Content/cupertino/jquery-ui-1.7.2.custom.css" 
          rel="stylesheet" type="text/css" />
    <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
    <script src="../../Scripts/grid.locale-sp.js" type="text/javascript"></script>
    <script src="../../Scripts/jquery.jqGrid.min.js" type="text/javascript"></script>
</head>

Es importante incluir todos los archivos y en el orden correcto, de lo contrario el plugin no funcionará correctamente.

4. El modelo

En lugar de utilizar una base de datos, vamos a crear una simulación en memoria de un almacén de datos personales basado en la siguiente entidad:

public class Amigo
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public string Apellidos { get; set; }
    public DateTime FechaDeNacimiento { get; set; }
    public string Email { get; set; }
}

La lógica de negocio está implementada en la clase GestorDeAmigos, que incluye, además del constructor que carga los datos iniciales en el almacén (una colección de tipo List<Amigo> , los siguientes métodos:

public IEnumerable<Amigo> ObtenerAmigos(int pagina, int elementosPorPagina, 
                                        Func<Amigo, IComparable> orden, 
                                        Ordenacion ordenacion)
{
    IEnumerable<Amigo> datos;
    if (ordenacion==Ordenacion.Ascendente)
        datos = datosAmigos.OrderBy(orden);
    else
        datos = datosAmigos.OrderByDescending(orden);
 
    return datos.Skip((pagina - 1) * elementosPorPagina).Take(elementosPorPagina);
}
 
public int ContarAmigos()
{
    return datosAmigos.Count;
}

Del código anterior, sólo comentar los parámetros del método ObtenerAmigos:

  • pagina y elementosPorPagina, indican, respectivamente el número de página de datos a mostrar y el número de registros máximo a mostrar en cada una de ellas.
  • orden es una lambda que retorna la expresión por la cual ordenaremos los datos. Si estuviéramos utilizando consultas SQL directas o LINQ to SQL podríamos usar otros enfoques, como la construcción de queries dinámicas o Dynamic LINQ.
  • ordenacion indica, usando una enumeración, si el orden es ascendente o descendente.

Así, a continuación puede verse un ejemplo de lo simple que quedaría una llamada a este método, flexible pero con tipado fuerte:

var amigos = gestorDeAmigos.ObtenerAmigos(
                   1,                       // Página actual
                   25,                      // Registros por página
                   amigo=>amigo.Apellidos,  // Expresión de ordenación
                   Ordenacion.Ascendente    // Orden
             );

5. El controlador

Una de las principales ventajas que ofrece jqGrid es que la carga de los datos la realiza mediante peticiones Ajax. En la práctica, esto significa que irá lanzando llamadas a una acción de nuestro controlador para solicitarle la información conforme vaya necesitando datos, por ejemplo, durante la carga inicial de la página o cuando el usuario utiliza las herramientas de paginación, enviándole los siguientes parámetros:

  • sidx, el índice o nombre del campo de ordenación actual.
  • sord, “asc” o “desc”, indicando si el orden es ascendente o descendente.
  • page, el número de página actual.
  • rows, número de elementos a obtener para completar la página.

El retorno de este método de acción debe ser un objeto serializado en JSON (aunque también permite XML) y ha de ajustarse a un formato especificado en la documentación, que es el que jqGrid espera recibir, algo como:

{ 
  total: "xxx", 
  page: "yyy", 
  records: "zzz",
  rows : [
    {id:"1", cell:["cell11", "cell12", "cell13"]},
    {id:"2", cell:["cell21", "cell22", "cell23"]},
      ...
  ]
}

El método de obtención de datos,  nuestra acción, seguirá normalmente el patrón recogido en el código mostrado a continuación. Si os fijáis, lo único que estamos haciendo es reproducir fielmente esta estructura anterior en un objeto anónimo, de forma que  al transformarlo en JSON (gracias al retorno de tipo JsonResult) se genere justo lo que necesitamos:

public ActionResult ObtenerDatosGrid(string sidx, string sord, int page, int rows)
{
    var datos = [...]          // Obtener datos del modelo
    var totalRegistros = [...] // Contar todos los registros
 
    int totalPages = (int)Math.Ceiling((decimal)totalRegistros / (decimal)rows);
 
    var data = new
    {
        total = totalPages,        // Total de páginas
        page = page,               // Página actual
        records = totalRegistros,  // Total de registros (obtenido del modelo)
        rows = from a in datos     // Datos de filas
               select new {
                   id = a.Id,                // ID único de la fila
                   cell = new string[] {     // Array de celdas de la fila
                       a.Apellidos,                             // Primera columna,            
                       a.Nombre,                                // Segunda columna,
                       a.FechaDeNacimiento.ToShortDateString(), // Tercera columna,
                       a.Email                                  // Cuarta columna  
                   }
               }
    };
    return Json(data);
}

Nuestro ejemplo se ajusta totalmente al patrón anterior, aunque incluiremos código extra para tener en cuenta las ordenaciones indicadas por los parámetros sidx y sord en la invocación al modelo. Observad cómo construimos las expresiones de ordenación:

public ActionResult ObtenerDatosGrid(string sidx, string sord, int page, int rows)
{
    // Establecemos la función de ordenación dependiendo del valor del 
    // parámetro "sidx", que es el campo de orden actual
    Func<Amigo, IComparable> funcOrden =
        sidx == "Apellidos" ? a => a.Apellidos :
        sidx == "FechaDeNacimiento" ? a => a.FechaDeNacimiento :
        sidx == "Email" ? a => a.Email :
        (Func<Amigo, IComparable>)(a => a.Id);
 
    // Establecemos si se trata de orden ascendente o descendente, en
    // función del parámetro "sord", que puede valer "asc" o "desc"
    Ordenacion ordenacion = sord == "asc" ? Ordenacion.Ascendente : Ordenacion.Descendente;
 
    // Usamos el modelo para obtener los datos
    var datos = gestorDeAmigos.ObtenerAmigos(page, rows, funcOrden, ordenacion);
    int totalRegistros = gestorDeAmigos.ContarAmigos();
    ... 
}

6. La vista

En la vista debemos crear un elemento contenedor, concretamente una tabla XHTML con un identificador único que después usaremos para indicarle al plugin dónde tiene que actuar su magia. Si además, como bastante habitual, pensamos incluir herramientas de paginación, tendremos que incluir también un contenedor para la misma:

<table id="list"></table>
<div id="pager"></div>    

El siguiente paso es inicializar jqGrid cuando se termine de cargar la página, para lo que crearemos un código javascript como el mostrado a continuación, en el que se invoca la función de activación del plugin sobre la tabla cuyo ID se indica en el selector (“#list“), y especificando el comportamiento deseado en los parámetros que le siguen:

script type="text/javascript">
    jQuery(document).ready(function() {
        jQuery("#list").jqGrid({
            url: '<%= Url.Action("ObtenerDatosGrid") %>',
            datatype: 'json',
            mtype: 'GET',
            colNames: ['Apellidos', 'Nombre', 'Fecha Nac.', 'Email'],
            colModel: [
              { index: 'Apellidos', width: 150, align: 'left' },
              { index: 'Nombre', width: 150, align: 'left', sortable: false },
              { index: 'FechaDeNacimiento', width: 80, align: 'center' },
              { index: "Email", width: 120, align: 'left'}],
            pager: jQuery('#pager'),
            rowNum: 20,
            rowList: [20, 50, 100],
            sortname: 'Apellidos',
            sortorder: 'asc',
            viewrecords: true,
            imgpath: '/content/cupertino/images',
            caption: 'Agenda personal',
            height: 400,
            width: 900,
            onSelectRow: function(id) {
                alert("Pulsado Id: " + id);
            }
        });
    }); 
</script>    

Revisamos rápidamente el significado de los distintos parámetros:

  • url, la dirección de la acción que suministrará los datos.
  • datatype, que indica el formato de intercambio de datos.
  • mtype , el verbo HTTP (get o post) que se utilizará para las peticiones.
  • colnames, un array con los títulos de las columnas.
  • colmodel, un array en el que cada elemento es un objeto que define
    • index: el nombre del campo que recibiremos en el método de acción indicando la ordenación actual.
    • width: el ancho, en píxeles.
    • align: alineación de la columna.
    • sortable: si la columna se puede utilizar como criterio de ordenación.
  • pager, el elemento sobre el que se compondrá la herramienta de paginación.
  • rownum, número de elementos por página.
  • rowlist, elementos por página mostrados en un desplegable para que el usuario lo ajuste.
  • sortname, el campo de ordenación por defecto.
  • sortorder, si la ordenación por defecto es ascendente o descendente.
  • viewrecords, si se muestra el total de registros en la herramienta de paginación.
  • imgpath , ruta hacia la carpeta de imágenes del tema elegido.
  • caption, el título del grid.
  • height, width, alto y ancho del grid, en píxeles.
  • onSelectRow, función anónima ejecutada cuando el usuario selecciona una fila. Recibe como parámetro el identificador único del registro.

Y… ¡Voilá!

Demo de jqGrid en acción¡Hasta aquí hemos llegado! Hemos visto cómo utilizar el magnífico jqGrid en un escenario simple, paso a paso, comenzando por la descarga de los componentes, preparando el proyecto para poder utilizarlos y, finalmente, implementando el modelo, el controlador, y la vista.

Pero jqGrid no es sólo eso, ni mucho menos. No hemos visto más que la punta del iceberg: el plugin permite realizar búsquedas, edición, anidación de grids, árboles, escenarios maestro-detalle… en fin, una maravilla.

jqGrid se distribuye bajo licencia dual GPL y MIT, y puede ser utilizado tanto en proyectos comerciales como libres. Dispone de una documentación bastante razonable, y muchos ejemplos para que podamos apreciar sus virtudes.

Descargar proyecto de demostración:

Publicado en: Variable not found.

martes, 20 de octubre de 2009

El listo Los “ilities”, cuya traducción a nuestro idioma podría ser algo así como “ilidades”, son atributos que definen aspectos no funcionales y normalmente asociados con requisitos técnicos y cualidades de un sistema o componente software. El nombre viene del sufijo –ilidad, que, como podréis ver a continuación, es común a la práctica totalidad de ellos.

He encontrado en la Wikipedia una interesante (aunque no exhaustiva) relación ilidades, que podemos utilizar a la hora de intentar transmitir las características de un sistema… o simplemente para aparentar ser auténticos expertos en una reunión de trabajo ;-)

  1. Accesibilidad, es decir, el hecho ser accesible a muchas personas, con independencia de sus características, dispositivos utilizados, etc.
  2. Reportabilidad, capacidad para monitorizar y registrar las acciones del usuario.
  3. Exactitud, grado de distancia desde los resultados ofrecidos por un sistema a los valores entendidos como reales.
  4. Adaptabilidad, facilidad que presenta un software para ser adaptado a necesidades concretas.
  5. Administrabilidad, indica la capacidad de un sistema o componente para ser administrado y gestionado.
  6. Asequibilidad, que indica que, atendiendo a su coste, el sistema puede ser adquirido o conseguido por los clientes.
  7. Auditabilidad, transparencia frente a sistemas de auditoría.
  8. Disponibilidad, grado en el que un sistema está disponible, operativo y funcional.
  9. Credibilidad, que es considerado subjetiva y objetivamente como una fuente de información fiable.
  10. Conformidad con estándares, grado de cercanía a las prácticas o técnicas aceptadas como estándares.
  11. Compatibilidad, que funciona en un escenario determinado, o permite sustituir otros sistemas equivalentes.
  12. Componibilidad, capacidad para combinar el software con otros elementos para componer sistemas no previstos inicialmente.
  13. Configurabilidad, esto es, que dispone de mecanismos que permiten configurar algunos aspectos de su comportamiento.
  14. Corrección, que indica que el software actúa conforme a sus especificaciones.
  15. Personalizabilidad, capacidad de un sistema para ser personalizado a las preferencias de sus usuarios.
  16. Degradabilidad, capacidad para mantener las funcionalidades básicas en condiciones adversas.
  17. Demostrabilidad, aplicable a aquellos sistemas cuyo funcionamiento puede ser demostrado.
  18. Dependabilidad, que define la probabilidad de que el sistema complete su misión, dado que estaba disponible en el comienzo de la misma.
  19. Desplegabilidad, capacidad de un software para ser desplegado en un escenario.
  20. Distribuibilidad, capacidad o facilidad de un componente o sistema para ser distribuido.
  21. Durabilidad, mantenimiento de las funciones con el paso del tiempo.
  22. Efectividad, o capacidad para lograr el efecto deseado.
  23. Eficiencia, capacidad para lograr el efecto deseado con el menor consumo posible de recursos.
  24. Evolucionabilidad, grado de facilidad con la que es posible hacer evolucionar un sistema para adaptarse a nuevas condiciones.
  25. Extensibilidad, capacidad de un software para ser ampliado con nuevas funciones sin modificar su estructura básica.
  26. Fidelidad, que ofrece precisión y confianza en la representación de la realidad.
  27. Flexibilidad, que puede adaptarse con facilidad a cambios en el entorno.
  28. Instalabilidad, facilidad para ser instalado en un entorno  determinado.
  29. Integridad, capacidad para evitar y sobreponerse a errores en la información que maneja.
  30. Intercambiabilidad, que indica que un componente puede ser sustituido por otro sin modificar aquellos que lo utilizan.
  31. Interoperabilidad, capacidad que tiene un sistema para intercambiar información con otro.
  32. Aprendibilidad, es decir, la capacidad que presenta un software para enseñar su propio manejo al usuario.
  33. Mantenibilidad, o facilidad con la que se puede corregir, modificar o ampliar un software.
  34. Gestionabilidad, facilidad para gestionar la complejidad de un sistema.
  35. Movilidad, posibilidad que tiene un sistema de funcionar en distintas ubicaciones geográficas.
  36. Modularidad, grado en el que un software está internamente dividido en bloques independientes.
  37. Nomadicidad, capacidad de un sistema para adaptarse de forma transparente a una situación de desconexión por cambio de ubicación-
  38. Operabilidad, capacidad de un software para funcionar y cumplir su misión.
  39. Portabilidad, la facilidad con la que un software puede ser transferido a otro entorno software o hardware.
  40. Precisión, nivel de detalle con el que un sistema es capaz de manejar información.
  41. Predictibilidad, grado de certeza del comportamiento del sistema ante un escenario determinado.
  42. Recuperabilidad, capacidad de un sistema de restablecer un nivel apropiado de servicio y recuperar sus datos en caso de producirse fallos.
  43. Fiabilidad, es la habilidad de un software para realizar sus funciones de forma correcta bajo unas condiciones determinadas.
  44. Repetibilidad, es la capacidad de generar el mismo resultado si no cambian las condiciones.
  45. Reproducibilidad, es el grado de precisión que se obtiene al intentar reproducir un comportamiento del sistema.
  46. Responsividad, velocidad con la que un sistema realiza una tarea, por ejemplo responder a la entrada del usuario.
  47. Reusabilidad, facilidad con la que un componente o sistema puede ser reutilizado para añadir funcionalidades o características en un nuevo sistema.
  48. Robustez, capacidad para continuar la operación aún en el caso de producirse errores inesperados.
  49. Seguridad, capacidad de un software para conseguir niveles aceptables de daños a personas, negocios, software, propiedades o entorno.
  50. Escalabilidad, facilidad con la que un sistema puede ser adaptado para satisfacer necesidades mayores de carga de trabajo.
  51. Uniformidad, grado de presentación como una estructura consistente, aunque el sistema incluya diversas tecnologías.
  52. Soportabilidad, que hace referencia a la habilidad del soporte técnico para dar soporte a los productos.
  53. Asegurabilidad, capacidad para asegurar distintos niveles de un sistema.
  54. Simplicidad, o ausencia de complicaciones y dificultades.
  55. Estabilidad, capacidad de un software para funcionar períodos del tiempo sin parar o funcionar incorrectamente.
  56. Comprobabilidad, el grado en el que un componente soporta la realización de pruebas para validar su corrección.
  57. Oportunidad, es decir, el hecho de estar disponible en el momento en el que debe estarlo.
  58. Ubicuidad, capacidad de un software para estar presente en todas partes, en cualquier momento.
  59. Comprensibilidad, facilidad con la que un software puede ser comprendido a los distintos niveles.
  60. Usabilidad, facilidad con la que un usuario puede utilizar un sistema software para conseguir un objetivo. 

Los términos originales estaban en inglés y algunos no eran fácilmente traducibles, así que en más de una ocasión he tenido que echar mano de la creatividad y patear a la RAE y todos sus integrantes ;-D

Publicado en: Variable not found.

miércoles, 14 de octubre de 2009

No recuerdo cuándo exactamente, debió ser cuando estuve comentando la presentación de esa misteriosa criatura llamada Google Wave, pero al parecer solicité una invitación para probar el producto, y hoy me ha llegado.

Tras seguir el enlace recibido por email, he instalado Chrome Frame, el complemento para Internet Explorer que permite incrustar el navegador Chrome, y ya se puede decir que estoy en la onda ;-)

Google Wave en acción

A ver si un día de estos tengo un rato libre para probarlo en profundidad, por que la verdad es que tengo ganas de comprobar de primera mano si el revuelo que ha levantado Wave en la red está fundado o no. Seguiremos informando al respecto. :-)

Por cierto, tengo ocho invitaciones para otros tantos amigos que quieran probarlo, sólo tenéis que hacerme llegar vuestra dirección de correo electrónico e iré enviando las invitaciones en riguroso orden de llegada.

Uf, ya sólo tengo siete, vais a tener que daros prisa…


Actualizado (15-oct): ya he informado a los afortunados por email, y enviado sus datos a Google Wave, sólo les queda esperar a que les lleguen las invitaciones. Gracias a todos por participar.

Publicado en: Variable not found.

lunes, 12 de octubre de 2009

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:


  1. 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.

  2. 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 ;-)

  3. Añadimos a la carpeta /scripts del proyecto la librería xVal.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 archivo xVal.Messages.es-ES.js, disponible en la carpeta “internationalization” del zip.

  4. Copiamos al directorio /bin de nuestra aplicación el ensamblado xVal.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.

  5. 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.

Diálogo 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.


  1. Dado que vamos a declarar las restricciones utilizando los atributos data annotations, hay que añadir referencia en el proyecto a  System.ComponentModel.DataAnnotations.

  2. 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.


  3. 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 entidad Recluta, 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 tipo RulesException (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ón RulesException lanzada desde validar(), 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 al ModelState 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.
Y esto es todo. Con estas operaciones, ya tenemos las validaciones listas en el lado del servidor, pasemos ahora a solucionarlas en cliente.

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:

  1. 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>


  2. 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.