July 2010 - Artículos

A propósito de un artículo publicado anteriormente, basado en las técnicas de validación aplicadas al control DataForm de Silverlight, un lector preguntaba acerca de la posibilidad de utilizar este mecanismo de validación, para comprobar si un determinado valor existe en la base de datos utilizada como fuente de datos del DataForm, evitando con ello que el usuario pudiera introducir el mismo valor más de una vez.

Tal y como Jeff Handley (uno de los integrantes del equipo de desarrollo de WCF RIA Services) menciona en su blog, con Silverlight 4 este tipo de validación resulta ahora muy sencillo de implementar, como veremos en este artículo a través de un ejemplo práctico.

En primer lugar, al igual que en otros artículos de este blog en los que hemos abordado la validación con el control DataForm, crearemos un proyecto en Visual Studio 2010 con el nombre ValidacionDatos, que tenga como fuente de datos la base de datos PruebasSL. El código fuente del proyecto y el script de la base de datos se encuentran disponibles aquí.

También añadiremos todos los elementos necesarios para poner en funcionamiento un formulario de datos: modelo de datos, servicio de dominio, contexto de dominio, control DomainDataSource y el propio control DataForm, que será alimentado con el contenido de la tabla Customer de la base de datos PruebasSL, en forma de colección de entidades.

 

Comprobación de valores en el origen de datos desde el servicio de dominio

A continuación añadiremos al servicio de dominio un método denominado ExisteCustomerPorApellido, que emplearemos para comprobar si ya existe en la tabla Customer, un registro con el valor del campo LastName que pasamos como parámetro.

[EnableClientAccess()]
public class PruebasSLDomainService : LinqToEntitiesDomainService<PruebasSLEntities>
{
    //....
    public bool ExisteCustomerPorApellido(string sApellido)
    {
        bool bResultado;
        bResultado = this.ObjectContext.Customers.Where(oCustomer => oCustomer.LastName == sApellido).Any();
        return bResultado;
    }
}

 

Soporte de validación personalizada en entidades

La nueva interfaz INotifyDataErrorInfo, introducida en Silverlight 4, así como las mejoras realizadas a la clase Entity, en las que la propiedad ValidationErrors pasa a ser una colección a la que podemos añadir nuestros errores de validación (instancias de ValidationResult) personalizados, permiten incluir dichos errores en el mecanismo de evaluación de errores existente, como veremos en los próximos apartados.

 

Creación del validador

En nuestro siguiente paso escribiremos el código de la clase que se encargará de realizar la validación contra la base de datos, para lo que agregaremos al proyecto Web de la solución una clase llamada ApellidoCustomerValidador, que situaremos en un archivo con el nombre ApellidoCustomerValidador.shared.cs (la creación de un archivo con la terminación shared.cs, generará automáticamente una versión de este código en el lado cliente de la aplicación), y que comenzaremos a codificar de la siguiente manera.

using System.ComponentModel.DataAnnotations;

namespace ValidacionDatos.Web
{
    public class ApellidoCustomerValidador
    {
        public static ValidationResult ExisteApellido(string sApellido, ValidationContext oValidCtx )
        {
            ValidationResult oValidRes = new ValidationResult("El apellido ya existe",
                new string[] { oValidCtx.MemberName });

            PruebasSLDomainService oDomainService = new PruebasSLDomainService();
            if (oDomainService.ExisteCustomerPorApellido(sApellido))
            {
                return oValidRes;
            }

            return ValidationResult.Success;
        }
    }
}

 

Código de validación para la capa cliente de la aplicación

El código que acabamos de escribir para la clase ApellidoCustomerValidador se orienta a su ejecución desde la capa servidora de la aplicación; sin embargo, nuestro objetivo aquí es distinto, ya que pretendemos que la validación se realice desde el lado cliente, pero invocando un método que realice una comprobación en la base de datos, por lo que el proceso de validación deberá efectuarse de forma asíncrona.

Un inconveniente añadido reside en el hecho de que no podemos escribir el código de validación para la capa cliente de la aplicación en la clase ApellidoCustomerValidador, situada en el archivo ApellidoCustomerValidador.shared.cs del proyecto Silverlight de la solución, puesto que perderíamos dicho código en cuanto recompiláramos la solución, lo que nos obliga a escribirlo en el archivo del mismo nombre situado en el proyecto Web. En ese caso, ¿cómo indicamos qué parte del código de la clase debe ejecutarse en el lado cliente y qué parte en servidor?.

La respuesta viene de la mano de las directivas de compilación condicional #if...#else...#endif; las cuales, junto con el también símbolo de compilación condicional SILVERLIGHT, definido en las propiedades  del proyecto, nos permitirán ejecutar bloques de código en función de la capa de la aplicación que esté en ejecución. La siguiente imagen muestra la pestaña de propiedades del proyecto donde está definido el mencionado símbolo. 

A continuación vemos el código al completo de la clase de validación personalizada, incluyendo aquellas líneas que se ejecutarán en cliente gracias a las directivas de compilación. Centrándonos en este código cliente, lo que hacemos en él es crear una instancia del contexto de dominio, para llamar a continuación a su método ExisteCustomerPorApellido, al que pasaremos como parámetro la cadena con el valor del apellido, que recibe el método ExisteApellido del validador.

El método ExisteCustomerPorApellido retorna un tipo InvokeOperation<bool>. Al finalizar la ejecución del mencionado método se desencadena el evento Completed en InvokeOperation<bool>, y mediante el operador lambda (=>) codificamos en línea el comportamiento de este evento, donde consultamos el valor InvokeOperation.Value devuelto por el método ExisteCustomerPorApellido. En el caso de que dicho valor sea true, obtenemos la instancia de la entidad Customer que estamos editando desde el DataForm, y añadimos a su colección ValidationErrors el objeto ValidationResult que mostrará a través de la interfaz de usuario un mensaje, indicando que el valor introducido en el campo del formulario de datos ya existe en la fuente de datos.

using System.ComponentModel.DataAnnotations;

#if SILVERLIGHT
using System.ServiceModel.DomainServices.Client;
#endif

namespace ValidacionDatos.Web
{
    public class ApellidoCustomerValidador
    {
        public static ValidationResult ExisteApellido(string sApellido, ValidationContext oValidCtx)
        {
            ValidationResult oValidRes = new ValidationResult("El apellido ya existe",
                new string[] { oValidCtx.MemberName });

#if SILVERLIGHT
            // ejecución en la capa cliente
            PruebasSLDomainContext oDomainContext = new PruebasSLDomainContext();
            InvokeOperation<bool> oInvkOpBool = oDomainContext.ExisteCustomerPorApellido(sApellido);
            oInvkOpBool.Completed += (oSender, oEventArgs) => 
            {
                if (oInvkOpBool.Value)
                {
                    Entity oEntity = (Entity)oValidCtx.ObjectInstance;
                    oEntity.ValidationErrors.Add(oValidRes);
                }
            };
#else
            // ejecución en la capa servidora
            PruebasSLDomainService oDomainService = new PruebasSLDomainService();
            if (oDomainService.ExisteCustomerPorApellido(sApellido))
            {
                return oValidRes;
            }
#endif

            return ValidationResult.Success;
        }
    }
}

 

Aplicando el validador a los metadatos

Como paso final de todo el proceso debemos editar la clase CustomerMetadata, correspondiente a los metadatos de la entidad, que se encuentra en el proyecto Web de la solución, y calificar la propiedad LastName con el atributo CustomValidation, pasando al constructor de este atributo el tipo correspondiente a la clase ApellidoCustomerValidador, y una cadena con el nombre del método que realiza la validación: ExisteApellido.

[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
    //....
    internal sealed class CustomerMetadata
    {
        //....
        [CustomValidation(typeof(ApellidoCustomerValidador), "ExisteApellido")]
        public string LastName { get; set; }
        //....
    }
}

La siguiente imagen muestra el DataForm en ejecución, visualizando el mensaje de error.

 

Conclusión

En el presente artículo hemos ilustrado los pasos necesarios para realizar el proceso de validación contra una fuente de datos mediante el control DataForm de Silverlight. Esta técnica se une al resto de artículos que sobre validación hemos publicado en este blog. Espero que os resulte de ayuda.

Un saludo.

En un artículo anterior sobre validación de datos con el control DataForm, explicábamos las técnicas que a disposición del desarrollador, existen para efectuar el conjunto de operaciones principales de validación de datos mediante este control, consistentes mayoritariamente, en la utilización de diversos atributos pertenecientes al espacio de nombres DataAnnotations.

Con el uso de los mencionados atributos podemos cubrir el espectro de posibilidades de validación que contemplan los escenarios más comunes. Sin embargo, suele ocurrir con frecuencia, que para ciertos datos o combinaciones de datos, necesitamos una validación con unas características especiales, que pueden no encajar en la casuística estándar que se nos proporciona "de serie". Es en este tipo de situaciones, donde debemos optar por la creación de una validación a medida o personalizada, que cubra los casos que la validación estándar no alcanza a solventar.

Tal tipo de validación conforma el objetivo de este artículo, en el que explicaremos los pasos a seguir para lograr su implementación mediante el control DataForm.

Basándonos en el origen de datos propuesto en el artículo mencionado anteriormente, crearemos un proyecto en Visual Studio 2010 de tipo Silverlight Application con el nombre ValidacionPersonalizada (cuyo código fuente podemos descargar aquí). Al igual que en dicho artículo (recomendamos su consulta para un mayor detalle), el acceso y manipulación de datos estará compuesto por un modelo de datos, servicio de dominio, contexto de dominio, control DomainDataSource y control DataForm.

Una vez que tengamos el control DataForm preparado para realizar las operaciones de visualización y edición de datos, nos pondremos manos a la obra para desarrollar una validación personalizada sobre el campo FirstName del formulario, que esté basada en las siguientes condiciones: deberá empezar por mayúscula y tener una longitud mayor de cinco caracteres.

 

Archivos de código compartidos

En primer lugar, nos posicionaremos en el proyecto Web de la solución y añadiremos al mismo un nuevo archivo con el nombre PruebasSLDomainService.shared.cs. El motivo de emplear esta nomenclatura a la hora de denominar el archivo, radica en que al ser compilada la solución, se generará en el proyecto Silverlight una copia o versión cliente (compartida) del código contenido en el archivo original situado en el proyecto Web servidor.

 

 

La clase ValidationResult

A continuación abriremos el archivo PruebasSLDomainService.shared.cs, y escribiremos el código de una clase que llamaremos NombreCustomerValidador, conteniendo un método con el nombre EsNombreValido, en el que introduciremos la lógica de validación que emplearemos en el campo FirstName del DataForm. El requisito de dicho método consiste en devolver un tipo ValidationResult, que será evaluado por el mecanismo de validación, mostrando su resultado a través del formulario de datos.

En el caso de cumplirse los criterios de validación, se devolverá el campo ValidationResult.Success para indicar dicha situación. Si la validación resulta fallida, devolveremos una nueva instancia de ValidationResult, en cuyo constructor pasaremos el texto con el mensaje informativo para el usuario, y un array con los nombres de las propiedades a las que asociaremos nuestra validación personalizada. Dichas propiedades pertenecerán a la entidad que editaremos mediante el DataForm, y en nuestro caso particular solamente se tratará de la propiedad FirstName.

using System;
using System.ComponentModel.DataAnnotations;

namespace ValidacionPersonalizada.Web
{
    public static class NombreCustomerValidador
    {
        public static ValidationResult EsNombreValido(string sNombre, ValidationContext oValidationCtx)
        {
            if (char.IsUpper(sNombre, 0) && sNombre.Length > 5)
            {
                return ValidationResult.Success;
            }
            else
            {
                return new ValidationResult("El nombre debe empezar por mayúscula y tener más de cinco caracteres",
                    new string[] { "FirstName" });
            }
        }
    }
}

 

Aplicando el validador a los metadatos

Como siguiente paso modificaremos la clase CustomerMetadata (contenida dentro de la clase Customer), situada en el archivo PruebasSLDomainService.metadata.cs, calificando su propiedad FirstName con el atributo CustomValidation, al que pasaremos como parámetros el tipo correspondiente a la clase que acabamos de escribir, que proporciona nuestra validación personalizada; y el nombre del método que contiene la lógica de validación.

internal sealed class CustomerMetadata
{
    //....
    [Display(Order = 2)]
    [CustomValidation(typeof(NombreCustomerValidador), "EsNombreValido")]
    public string FirstName { get; set; }
    //....
}

De esta forma, el control (DataForm) que actúe como interfaz de usuario para la edición de la clase Customer, al editar el valor de la propiedad FirstName, utilizará el mecanismo de validación que hemos asociado a través de este atributo.

 

Probando la validación

Llegados a este punto, solamente queda ejecutar la aplicación, y comprobar que la lógica de validación que hemos desarrollado, funciona correctamente para el campo FirstName del formulario de datos. En la siguiente imagen vemos cómo la validación falla, mostrando el mensaje informativo al usuario.

 

Sin embargo, en esta otra imagen, dado que las condiciones de validación se cumplen, no se muestra el bloque del resumen de validación cuando el campo FirstName pierde el foco.

 

Habiendo verificado que nuestro proceso de validación personalizada funciona correctamente, finalizamos aquí este artículo, que esperamos sirva como complemento de los procesos de validación, cuando esta no pueda ser realizada utilizando los medios habituales disponibles. Espero que os sirva de ayuda.

Un saludo 

Publicado por Luis Miguel Blanco | con no comments