Compartir a través de


Validación

Sugerencia

Este contenido es un extracto del libro electrónico "Patrones de aplicaciones empresariales con .NET MAUI", disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Miniatura de la portada del libro electrónico

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 adecuada aplica reglas de negocio y podría ayudar a evitar 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 multiplataforma eShop 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 imagen siguiente se muestran las clases implicadas en la validación en la aplicación multiplataforma eShop.

Clases de validación en la aplicación multiplataforma eShop.

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. En el siguiente código se muestra la implementación de ValidatableObject<T>.

using CommunityToolkit.Mvvm.ComponentModel;
namespace eShop.Validations;
public class ValidatableObject<T> : ObservableObject, IValidity
{
    private IEnumerable<string> _errors;
    private bool _isValid;
    private T _value;
    public List<IValidationRule<T>> Validations { get; } = new();
    public IEnumerable<string> Errors
    {
        get => _errors;
        private set => SetProperty(ref _errors, value);
    }
    public bool IsValid
    {
        get => _isValid;
        private set => SetProperty(ref _isValid, value);
    }
    public T Value
    {
        get => _value;
        set => SetProperty(ref _value, value);
    }
    public ValidatableObject()
    {
        _isValid = true;
        _errors = Enumerable.Empty<string>();
    }
    public bool Validate()
    {
        Errors = Validations
            ?.Where(v => !v.Check(Value))
            ?.Select(v => v.ValidationMessage)
            ?.ToArray()
            ?? Enumerable.Empty<string>();
        IsValid = !Errors.Any();
        return IsValid;
    }
}

La clase ObservableObject 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 booleano Check 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 multiplataforma eShop:

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

    public bool Check(T value) =>
        value is string str && !string.IsNullOrWhiteSpace(str);
}

El método Check devuelve un valor booleano que indica si el argumento de valor es nulo, está vacío o solo consta de caracteres de espacio en blanco.

Aunque la aplicación multiplataforma eShop 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>
{
    private readonly Regex _regex = new(@"^([w.-]+)@([w-]+)((.(w){2,3})+)$");

    public string ValidationMessage { get; set; }

    public bool Check(T value) =>
        value is string str && _regex.IsMatch(str);
}

El método Check devuelve un valor booleano que indica si el argumento de valor es una dirección de correo electrónico válida. 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. Que el patrón de expresión regular se haya encontrado en la cadena de entrada se puede determinar comparando value con Regex.IsMatch.

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 multiplataforma eShop, las propiedades del modelo de vista que requieren validación se declaran 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; private set; }
public ValidatableObject<string> Password { get; private set; }

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 multiplataforma eShop puede desencadenar la validación de una propiedad manualmente y, luego, automáticamente, cuando la propiedad cambia.

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 multiplataforma eShop cuando el usuario pulsa el botón Login en 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 LoginView, invocando el método Validate en cada instancia de ValidatableObject<T>. En el ejemplo de código siguiente se muestra el método Validate de la clase ValidatableObject<T>:

public bool Validate()
{
    Errors = _validations
        ?.Where(v => !v.Check(Value))
        ?.Select(v => v.ValidationMessage)
        ?.ToArray()
        ?? Enumerable.Empty<string>();

    IsValid = !Errors.Any();

    return IsValid;
}

Este método 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 desencadena automáticamente 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.

Visualización de los errores de validación

Para notificar al usuario los errores de validación, la aplicación multiplataforma eShop resalta el control que contiene los datos no válidos con un fondo rojo y muestra debajo un mensaje de error que informa al usuario de por qué los datos no son válidos. Cuando se corrigen los datos no válidos, el fondo vuelve al estado predeterminado y se quita el mensaje de error. La imagen siguiente muestra LoginView en la aplicación multiplataforma eShop cuando hay errores de validación.

Visualización de los errores de validación durante el inicio de sesión.

Resaltado de un control que contiene datos no válidos

.NET MAUI ofrece varias maneras de presentar información de validación a los usuarios finales, pero una de las formas más sencillas es mediante el uso de Triggers. Los elementos Triggers proporcionan una manera de cambiar el estado de nuestros controles, normalmente por su apariencia, en función de un evento o de un cambio de datos que se produce para un control. Para la validación, usaremos un elemento DataTrigger que escuchará los cambios generados desde una propiedad enlazada y responderá a ellos. Los controles Entry de LoginView se configuran mediante el código siguiente:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="WinUI" Value="{StaticResource WinUIEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <Entry.Triggers>
        <DataTrigger 
            TargetType="Entry"
            Binding="{Binding UserName.IsValid}"
            Value="False">
            <Setter Property="BackgroundColor" Value="{StaticResource ErrorColor}" />
        </DataTrigger>
    </Entry.Triggers>
</Entry>

DataTrigger especifica las siguientes propiedades:

Propiedad Descripción
TargetType Tipo de control al que pertenece el desencadenador.
Binding Marcado de datos Binding que proporcionará notificaciones de los cambios y el valor de la condición del desencadenador.
Value Valor de datos que se va a especificar cuando se ha cumplido la condición del desencadenador.

En Entry, se escucharán los cambios en la propiedad LoginViewModel.UserName.IsValid. Cada vez que esta propiedad genera un cambio, el valor se comparará con la propiedad Value establecida en DataTrigger. Si los valores son iguales, se cumplirá la condición del desencadenador y se ejecutarán los objetos Setter proporcionados a DataTrigger. Este control tiene un único objeto Setter que actualiza la propiedad BackgroundColor a un color personalizado definido mediante el marcado StaticResource. Cuando ya no se cumpla una condición Trigger, el control revertirá las propiedades establecidas por el objeto Setter a su estado anterior. Para más información sobre Triggers, consulte Documentación de .NET MAUI: Desencadenadores.

Visualización de 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 etiqueta 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 IEnumerable<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 multiplataforma eShop 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 de ValidatableObject<T>, que recupera las reglas de validación y las ejecuta en la propiedad de valor ValidatableObject<T>. 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.