Compartir vía


Validación en Aplicaciones empresariales

Nota:

Este libro se publicó en la primavera de 2017 y no se ha actualizado desde entonces. Mucho contenido del libro sigue siendo valioso, pero algunos de los materiales están obsoletos.

Cualquier aplicación que acepte la entrada de los usuarios debe asegurarse de que la entrada sea válida. Una aplicación podría, por ejemplo, comprobar si hay entradas que contengan solo caracteres en un intervalo determinado, es de una longitud determinada o coincide con un formato determinado. Sin validación, un usuario puede proporcionar datos que hacen que se produzca un error en la aplicación. La validación aplica reglas de negocio y evita que un atacante inserte datos malintencionados.

En el contexto del patrón Modelo-Vista-Modelo de vista (MVVM), a menudo se necesitará un modelo o un modelo de vista para realizar la validación de los datos e indicar cualquier error de validación en la vista para que el usuario pueda corregirlo. La aplicación móvil eShopOnContainers realiza la validación sincrónica del lado cliente de las propiedades del modelo de vista y notifica al usuario los errores de validación resaltando el control que contiene los datos no válidos y mostrando mensajes de error que informan al usuario de por qué los datos no son válidos. En la figura 6-1 se muestran las clases implicadas en la validación en la aplicación móvil eShopOnContainers.

Validation classes in the eShopOnContainers mobile app

Figura 6-1: Clases de validación en la aplicación móvil eShopOnContainers

Las propiedades del modelo de vista que requieren validación son de tipo ValidatableObject<T> y cada instancia de ValidatableObject<T> tiene reglas de validación agregadas a su propiedad Validations. La validación se invoca desde el modelo de vista llamando al método Validate de la instancia ValidatableObject<T>, que recupera las reglas de validación y las ejecuta en la propiedad ValidatableObject<T>Value. Los errores de validación se colocan en la propiedad Errors de la instancia de ValidatableObject<T> y la propiedad IsValid de la instancia de ValidatableObject<T> se actualiza para indicar si la validación se realizó correctamente o no.

La clase ExtendedBindableObject proporciona la notificación de los cambios de propiedad, por lo que un control Entry puede enlazar a la propiedad IsValid de la instancia de ValidatableObject<T> en la clase del modelo de vista para que se le notifique si los datos especificados son válidos o no.

Especificación de reglas de validación

Las reglas de validación se especifican mediante la creación de una clase que deriva de la interfaz IValidationRule<T>, que se muestra en el ejemplo de código siguiente:

public interface IValidationRule<T>  
{  
    string ValidationMessage { get; set; }  
    bool Check(T value);  
}

Esta interfaz especifica que una clase de regla de validación debe proporcionar un método booleanCheck que se usa para realizar la validación necesaria y una propiedad ValidationMessage cuyo valor es el mensaje de error de validación que se mostrará si se produce un error en la validación.

En el ejemplo de código siguiente se muestra la regla de validación IsNotNullOrEmptyRule<T>, que se usa para realizar la validación del nombre de usuario y la contraseña especificados por el usuario en LoginView cuando se usan servicios ficticios en la aplicación móvil eShopOnContainers:

public class IsNotNullOrEmptyRule<T> : IValidationRule<T>  
{  
    public string ValidationMessage { get; set; }  

    public bool Check(T value)  
    {  
        if (value == null)  
        {  
            return false;  
        }  

        var str = value as string;  
        return !string.IsNullOrWhiteSpace(str);  
    }  
}

El método Check devuelve un boolean que indica si el argumento value es null, vacío o solo consta de caracteres de espacio en blanco.

Aunque la aplicación móvil eShopOnContainers no la usa, en el ejemplo de código siguiente se muestra una regla de validación para validar las direcciones de correo electrónico:

public class EmailRule<T> : IValidationRule<T>  
{  
    public string ValidationMessage { get; set; }  

    public bool Check(T value)  
    {  
        if (value == null)  
        {  
            return false;  
        }  

        var str = value as string;  
        Regex regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");  
        Match match = regex.Match(str);  

        return match.Success;  
    }  
}

El método Check devuelve un boolean que indica si el argumento value es una dirección de correo electrónico válida o no. Esto se logra buscando en el argumento de valor la primera aparición del patrón de expresión regular especificado en el constructor Regex. Si el patrón de expresión regular se encuentra en la cadena de entrada puede determinarse comprobando el valor de la propiedad Success del objeto Match.

Nota:

La validación de propiedades a veces puede implicar propiedades dependientes. Un ejemplo de propiedades dependientes es cuando el conjunto de valores válidos de la propiedad A depende del valor determinado que se ha establecido en la propiedad B. Para comprobar que el valor de la propiedad A es uno de los valores permitidos, habría que recuperar el valor de la propiedad B. Además, cuando cambia el valor de la propiedad B, la propiedad A tendría que volver a validarse.

Adición de reglas de validación a una propiedad

En la aplicación móvil eShopOnContainers, las propiedades del modelo de vista que requieren validación se declaran como de tipo ValidatableObject<T>, donde T es el tipo de los datos que se van a validar. En el ejemplo de código siguiente se muestra un ejemplo de dos de estas propiedades:

public ValidatableObject<string> UserName  
{  
    get  
    {  
        return _userName;  
    }  
    set  
    {  
        _userName = value;  
        RaisePropertyChanged(() => UserName);  
    }  
}  

public ValidatableObject<string> Password  
{  
    get  
    {  
        return _password;  
    }  
    set  
    {  
        _password = value;  
        RaisePropertyChanged(() => Password);  
    }  
}

Para que se produzca la validación, se deben agregar reglas de validación a la colección Validations de cada instancia de ValidatableObject<T>, como se muestra en el ejemplo de código siguiente:

private void AddValidations()  
{  
    _userName.Validations.Add(new IsNotNullOrEmptyRule<string>   
    {   
        ValidationMessage = "A username is required."   
    });  
    _password.Validations.Add(new IsNotNullOrEmptyRule<string>   
    {   
        ValidationMessage = "A password is required."   
    });  
}

Este método agrega la regla de validación IsNotNullOrEmptyRule<T> a la colección Validations de cada instancia de ValidatableObject<T>, y se especifican valores para la propiedad ValidationMessage de la regla de validación. Esta propiedad determina el mensaje de error de validación que se mostrará si se produce un error en la validación.

Desencadenamiento de la validación

El enfoque de validación usado en la aplicación móvil eShopOnContainers puede desencadenar manualmente la validación de una propiedad y desencadenar automáticamente la validación cuando cambia una propiedad.

Desencadenamiento de la validación manualmente

La validación se puede desencadenar manualmente para una propiedad de modelo de vista. Por ejemplo, esto ocurre en la aplicación móvil eShopOnContainers cuando el usuario pulsa el botón Inicio de sesión del LoginView, cuando se usan servicios ficticios. El delegado de comandos llama al método MockSignInAsync en LoginViewModel, que invoca la validación mediante la ejecución del método Validate, que se muestra en el ejemplo de código siguiente:

private bool Validate()  
{  
    bool isValidUser = ValidateUserName();  
    bool isValidPassword = ValidatePassword();  
    return isValidUser && isValidPassword;  
}  

private bool ValidateUserName()  
{  
    return _userName.Validate();  
}  

private bool ValidatePassword()  
{  
    return _password.Validate();  
}

El método Validate realiza la validación del nombre de usuario y la contraseña especificados por el usuario en el LoginView, invocando el método Validate en cada instancia de ValidatableObject<T>. En el ejemplo de código siguiente se muestra el método de la clase ValidatableObject<T>:

public bool Validate()  
{  
    Errors.Clear();  

    IEnumerable<string> errors = _validations  
        .Where(v => !v.Check(Value))  
        .Select(v => v.ValidationMessage);  

    Errors = errors.ToList();  
    IsValid = !Errors.Any();  

    return this.IsValid;  
}

Este método borra la colección Errors y, a continuación, recupera las reglas de validación que se agregaron a la colección Validations del objeto. El método Check de cada regla de validación recuperada se ejecuta y el valor de propiedad ValidationMessage de cualquier regla de validación que no valide los datos se agrega a la colección Errors de la instancia de ValidatableObject<T>. Por último, se establece la propiedad IsValid y su valor se devuelve al método que realiza la llamada, que indica si la validación se realizó correctamente o no.

Desencadenamiento de la validación cuando cambian las propiedades

La validación también se puede desencadenar cada vez que cambia una propiedad enlazada. Por ejemplo, cuando un enlace bidireccional de LoginView establece la propiedad UserName o Password, se desencadena la validación. En el ejemplo de código siguiente se muestra cómo se produce esto:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">  
    <Entry.Behaviors>  
        <behaviors:EventToCommandBehavior  
            EventName="TextChanged"  
            Command="{Binding ValidateUserNameCommand}" />  
    </Entry.Behaviors>  
    ...  
</Entry>

El control Entry enlaza a la propiedad UserName.Value de la instancia de ValidatableObject<T> y la colección Behaviors del control tiene una instancia de EventToCommandBehavior agregada. Este comportamiento ejecuta ValidateUserNameCommand en respuesta al evento [TextChanged] que se desencadena en Entry, que se genera cuando cambia el texto de Entry. A su vez, el delegado ValidateUserNameCommand ejecuta el método ValidateUserName, que ejecuta el método Validate en la instancia de ValidatableObject<T>. Por lo tanto, cada vez que el usuario escribe un carácter en el control Entry para el nombre de usuario, se realiza la validación de los datos especificados.

Para obtener más información sobre los comportamientos, consulte Implementación de comportamientos.

Visualización de errores de validación

La aplicación móvil eShopOnContainers notifica al usuario los errores de validación resaltando el control que contiene los datos no válidos con una línea roja y mostrando un mensaje de error que informa al usuario de por qué los datos no son válidos debajo del control que contiene los datos no válidos. Cuando se corrigen los datos no válidos, la línea cambia a negro y se quita el mensaje de error. En la figura 6-2 se muestra LoginView en la aplicación móvil eShopOnContainers cuando hay errores de validación.

Displaying validation errors during login

Figura 6-2: Visualización de errores de validación durante el inicio de sesión

Resaltado de un control que contiene datos no válidos

El comportamiento adjunto LineColorBehavior se usa para resaltar controles Entry en los que se han producido errores de validación. En el ejemplo de código siguiente se muestra cómo se adjunta el comportamiento LineColorBehavior adjunto a un control Entry:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="UWP" Value="{StaticResource UwpEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    ...
</Entry>

El control Entry consume un estilo explícito, que se muestra en el ejemplo de código siguiente:

<Style x:Key="EntryStyle"  
       TargetType="{x:Type Entry}">  
    ...  
    <Setter Property="behaviors:LineColorBehavior.ApplyLineColor"  
            Value="True" />  
    <Setter Property="behaviors:LineColorBehavior.LineColor"  
            Value="{StaticResource BlackColor}" />  
    ...  
</Style>

Este estilo establece las propiedades adjuntas ApplyLineColor y LineColor del comportamiento adjunto de LineColorBehavior en el control Entry. Para obtener más información sobre los estilos, consulta Estilos.

Cuando se establece el valor de la propiedad adjunta ApplyLineColor o cambia, el comportamiento adjunto LineColorBehavior ejecuta el método OnApplyLineColorChanged, que se muestra en el ejemplo de código siguiente:

public static class LineColorBehavior  
{  
    ...  
    private static void OnApplyLineColorChanged(  
                BindableObject bindable, object oldValue, object newValue)  
    {  
        var view = bindable as View;  
        if (view == null)  
        {  
            return;  
        }  

        bool hasLine = (bool)newValue;  
        if (hasLine)  
        {  
            view.Effects.Add(new EntryLineColorEffect());  
        }  
        else  
        {  
            var entryLineColorEffectToRemove =   
                    view.Effects.FirstOrDefault(e => e is EntryLineColorEffect);  
            if (entryLineColorEffectToRemove != null)  
            {  
                view.Effects.Remove(entryLineColorEffectToRemove);  
            }  
        }  
    }  
}

Los parámetros de este método proporcionan la instancia del control al que está asociado el comportamiento y los valores antiguos y nuevos de la propiedad adjunta ApplyLineColor. La clase EntryLineColorEffect se agrega a la colección Effects del control si la propiedad adjunta ApplyLineColor es true; de lo contrario, se quita de la colección Effects del control. Para obtener más información sobre los comportamientos, consulte Implementación de comportamientos.

La subclase EntryLineColorEffect de la clase RoutingEffect, y se muestra en el siguiente ejemplo de código:

public class EntryLineColorEffect : RoutingEffect  
{  
    public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")  
    {  
    }  
}

La clase RoutingEffect representa un efecto independiente de la plataforma que encapsula un efecto interno específico de la plataforma. Esto simplifica el proceso de eliminación del efecto, ya que no hay ningún acceso en tiempo de compilación a la información de tipo para un efecto específico de la plataforma. El EntryLineColorEffect llama al constructor de clase base, pasando un parámetro que consta de una concatenación del nombre del grupo de resolución y el identificador único especificado en cada clase de efecto específica de la plataforma.

En el ejemplo de código siguiente se muestra la implementación de eShopOnContainers.EntryLineColorEffect para iOS:

[assembly: ResolutionGroupName("eShopOnContainers")]  
[assembly: ExportEffect(typeof(EntryLineColorEffect), "EntryLineColorEffect")]  
namespace eShopOnContainers.iOS.Effects  
{  
    public class EntryLineColorEffect : PlatformEffect  
    {  
        UITextField control;  

        protected override void OnAttached()  
        {  
            try  
            {  
                control = Control as UITextField;  
                UpdateLineColor();  
            }  
            catch (Exception ex)  
            {  
                Console.WriteLine("Can't set property on attached control. Error: ", ex.Message);  
            }  
        }  

        protected override void OnDetached()  
        {  
            control = null;  
        }  

        protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)  
        {  
            base.OnElementPropertyChanged(args);  

            if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName ||  
                args.PropertyName == "Height")  
            {  
                Initialize();  
                UpdateLineColor();  
            }  
        }  

        private void Initialize()  
        {  
            var entry = Element as Entry;  
            if (entry != null)  
            {  
                Control.Bounds = new CGRect(0, 0, entry.Width, entry.Height);  
            }  
        }  

        private void UpdateLineColor()  
        {  
            BorderLineLayer lineLayer = control.Layer.Sublayers.OfType<BorderLineLayer>()  
                                                             .FirstOrDefault();  

            if (lineLayer == null)  
            {  
                lineLayer = new BorderLineLayer();  
                lineLayer.MasksToBounds = true;  
                lineLayer.BorderWidth = 1.0f;  
                control.Layer.AddSublayer(lineLayer);  
                control.BorderStyle = UITextBorderStyle.None;  
            }  

            lineLayer.Frame = new CGRect(0f, Control.Frame.Height-1f, Control.Bounds.Width, 1f);  
            lineLayer.BorderColor = LineColorBehavior.GetLineColor(Element).ToCGColor();  
            control.TintColor = control.TextColor;  
        }  

        private class BorderLineLayer : CALayer  
        {  
        }  
    }  
}

El método OnAttached recupera el control nativo del control Xamarin.FormsEntry y actualiza el color de línea llamando al método UpdateLineColor. La invalidación de OnElementPropertyChanged responde a los cambios de propiedad enlazables en el control Entry actualizando el color de línea si cambia la propiedad LineColor adjunta o la propiedad Height de la Entry cambia. Para obtener más información sobre los efectos, vea Efectos.

Cuando se escriben datos válidos en el control Entry, se aplicará una línea negra a la parte inferior del control para indicar que no hay ningún error de validación. En la figura 6-3 se muestra un ejemplo de esto.

Black line indicating no validation error

Figura 6-3: Línea negra que indica que no hay ningún error de validación

El control Entry también tiene un DataTrigger agregado a su colección Triggers. En el ejemplo de código siguiente se muestra el DataTrigger:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">  
    ...  
    <Entry.Triggers>  
        <DataTrigger   
            TargetType="Entry"  
            Binding="{Binding UserName.IsValid}"  
            Value="False">  
            <Setter Property="behaviors:LineColorBehavior.LineColor"   
                    Value="{StaticResource ErrorColor}" />  
        </DataTrigger>  
    </Entry.Triggers>  
</Entry>

Este DataTrigger supervisa la propiedad UserName.IsValid y, si su valor se convierte en false, ejecuta el Setter, que cambia la propiedad adjunta LineColor del comportamiento asociado de LineColorBehavior a rojo. En la figura 6-4 se muestra un ejemplo de esto.

Red line indicating validation error

Figura 6-4: Línea roja que indica el error de validación

La línea del control Entry permanecerá roja mientras los datos especificados no son válidos; de lo contrario, cambiará a negro para indicar que los datos especificados son válidos.

Para obtener más información sobre los desencadenadores, consulte Desencadenadores .

Mostrar mensajes de error

La interfaz de usuario muestra los mensajes de error de validación en los controles Label debajo de cada control cuyos datos no se pudieron validar. En el ejemplo de código siguiente se muestra el elemento Label que muestra un mensaje de error de validación, si el usuario no ha escrito un nombre de usuario válido:

<Label Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}}"  
       Style="{StaticResource ValidationErrorLabelStyle}" />

Cada Label enlaza a la propiedad Errors del objeto de modelo de vista que se está validando. La propiedad Errors se proporciona mediante la clase ValidatableObject<T>, y es de tipo List<string>. Dado que la propiedad Errors puede contener varios errores de validación, la instancia de FirstValidationErrorConverter se usa para recuperar el primer error de la colección y mostrarlo.

Resumen

La aplicación móvil eShopOnContainers realiza la validación sincrónica del lado cliente de las propiedades del modelo de vista y notifica al usuario los errores de validación resaltando el control que contiene los datos no válidos y mostrando mensajes de error que informan al usuario de por qué los datos no son válidos.

Las propiedades del modelo de vista que requieren validación son de tipo ValidatableObject<T> y cada instancia de ValidatableObject<T> tiene reglas de validación agregadas a su propiedad Validations. La validación se invoca desde el modelo de vista llamando al método Validate de la instancia ValidatableObject<T>, que recupera las reglas de validación y las ejecuta en la propiedad ValidatableObject<T>Value. Los errores de validación se colocan en la propiedad Errors de la instancia de ValidatableObject<T> y la propiedad IsValid de la instancia de ValidatableObject<T> se actualiza para indicar si la validación se realizó correctamente o no.