Validación de un origen de datos mediante el control DataForm de Silverlight

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.

6 Comentarios

  1. anonymous

    Interesante, Luismi !!! 🙂

    Eso de la compilación condicional…mmmm

  2. lmblanco

    Hola Enrique

    Muchas gracias por tu opinión, y ciertamente, la compilación condicional es un interesante invento 8-).

    Un saludo,
    Luismi

  3. anonymous

    Excelentes aportes Luis, estaré muy al pendiente de algún nuevo articulo.

    Gracias por darte el tiempo, saludos desde Perú.

  4. lmblanco

    Hola JDotNet

    Gracias por tu opinión y celebro que el post te resulte de interés.

    Un saludo.
    Luismi

  5. anonymous

    Hola Luis Miguel.
    Soy principiante en SL pero resulta que tengo un proyecto ya hecho dónde quiero meter la validación que implementaste.
    Mi proyecto tiene 3 capas y la tercera corresponde al modelo donde están alojados los metadatos.
    El problema resulta cuando quiero aplicar el validador a los metadatos ya que el validador no es reconocido porque se encuentra en otra capa.
    ¿Qué podría hacer para de cierta manera vincular estas 2 capas? :S

    Seguí tu ejemplo y no tuve ningun problema ya que el DomainService y los metadatos están en la misma capa.

    Saludos,
    espero que pueda ayudarme.

Responder a Cancelar respuesta

Tema creado por Anders Norén