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