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