Validaci贸n de formularios con Prism for Windows Runtime
- 8 minutos de lecturaContinuamos la serie de posts en el que estamos viendo las funcionalidades m谩s interesantes que nos proporciona Prism for Windows Runtime. En esta ocasi贸n vamos a ver que clases nos proporciona Prism para validar un modelo y c贸mo guardar el estado de validaci贸n para que pueda ser restaurado en caso de que la aplicaci贸n sea finalizada.
En la librer铆a Microsoft.Practices.Prism.StoreApps tenemos dos clases que nos proporcionan todo el soporte de validaci贸n: ValidatableBindableBase y BindableValidator. ValidatableBindableBase es la clase principal que contiene la propiedad Errors, una instancia de la clase BindableValidator que contiene todos los errores de validaci贸n. Adem谩s, en la soluci贸n de referencia AdventureWorks, tenemos varias clases que nos ayudar谩n a resaltar visualmente los errores de validaci贸n.
La primera decisi贸n que debemos tomar cuando queremos validar el valor de un campo, es si lo queremos hacer en el View Model o en el Model. Lo aconsejable es realizar la validaci贸n en el Model, ya que de hacerlo en el VM, muy posiblemente significar谩 que estamos duplicando las propiedades del modelo. Para comenzar con el ejemplo m谩s sencillo vamos a crear una clase de modelo que herede de ValidatableBindableBase y especificamos las reglas de validaci贸n a帽adiendo atributos DataAnnotation a las propiedades. La lista de los atributos que podemos utilizar los pod茅is encontrar en la documentaci贸n de System.ComponentModel.DataAnnotations de la MSDN. Y para evitar tener que exponer todas las propiedades en el VM, se expone en el VM una instancia de la clase UserInfo.
public class UserInfo : ValidatableBindableBase {
private string firstName;
[Required]
public string FirstName
{
get { return firstName; }
set { SetProperty(ref firstName, value); } }
}
public class UserInfoViewModel : ViewModel {
public MainPageViewModel() : this(new UserInfo())
{
}
public MainPageViewModel(UserInfo userInfo)
{
this.userInfo = userInfo;
}
private UserInfo userInfo;
public UserInfo UserInfo {
get
{
return userInfo;
}
set
{
SetProperty(ref userInfo, value);
}
}
}
En este ejemplo, solo tenemos una propiedad en el modlo que hemos marcado como obligatoria mediante el atributo Required. Este atributo indica que la validaci贸n fallar谩 si el campo est谩 nulo, contiene una cadena v谩lida o solo espacios en blanco.
El siguiente paso es mostrar visualmente el error de validaci贸n, para esto necesitamos que en el XAML contenga tres controles: la etiqueta del campo, el textbox y un textblock para mostrar el error de validaci贸n.
<TextBlock x:Name="FirstNameTitle"
Style="{StaticResource BasicTextStyle}"
Text="First Name" />
<TextBox x:Name="FirstNameValue"
Text="{Binding UserInfo.FirstName, Mode=TwoWay}"
behaviors:HighlightFormFieldOnErrors.PropertyErrors="{Binding UserInfo.Errors\[FirstName\]}" />
<TextBlock x:Name="ErrorFirstName"
Style="{StaticResource ErrorMessageStyle}"
Text="{Binding UserInfo.Errors\[FirstName\], Converter={StaticResource FirstErrorConverter}}"
/>
Lo nuevo en este c贸digo es que estamos utilizando la Attached Property HighlightFormFieldOnErrors.PropertyErrors y el converter FirstErrorConverter. La implementaci贸n de estas clases las encontraremos en la implementaci贸n de referencia AdventureWorks, ya que no forman parte de las DLL de Prism, as铆 que vamos a poder modificarlas seg煤n nuestras necesidades.
La attached property PropertyErrors est谩 enlazada con la propiedad Errors del modelo y se utiliza para comprobar si hay errores de validaci贸n en el campo y cambiar el estilo. Concretamente se establece el estilo HighlightFormFieldStyle y, si no hay errores, el estilo FormFieldStyle. Estos dos estilos no son est谩ndar y los tenemos que definir tambi茅n en un diccionario de recursos de nuestra soluci贸n. La otra clase que se utiliza es FirstErrorConverter, un converter para ocultar o mostrar el TextBlock de validaci贸n dependiendo de si hay o no hay errores de validaci贸n.
Esta es la implementaci贸n sencilla, la m谩s b谩sica. Pero la realidad es que en las aplicaciones reales, al final las validaciones simples son las menos frecuentes.
Validando propiedades dependientes
Lo normal es que tengamos validaciones en un campo que dependen del valor de otro campo. El ejemplo t铆pico es el de fecha inicio y fecha fin, donde la final no puede ser inferior a la inicial. En el siguiente ejemplo voy a utilizar otro caso bastante habitual, voy a a帽adir al modelo las propiedades Country e IBAN, que nos servir谩n para guardar la nacionalidad y el n煤mero de cuenta internacional. La caracter铆stica del IBAN es que comienza con dos caracteres que corresponden al c贸digo de pa铆s, as铆 que vamos a a帽adir la validaci贸n para que verifique que el c贸digo comienza con el c贸digo correcto de pa铆s. No voy a implementar toda la validaci贸n de IBAN, porque para este post carece de inter茅s.
public string CountryCode
{
get { return countryCode; }
set { SetProperty(ref countryCode, value); }
}
[Required]
[CustomValidation(typeof(UserInfo), "ValidateIBAN")]
public string IBAN
{
get { return iban; }
set { SetProperty(ref iban, value); }
}
En este c贸digo hemos definido las dos nuevas propiedades, y hemos aplicado el atributo CustomValidation a la propiedad IBAN. Mediante este atributo podemos indicar mediante en el segundo par谩metro el nombre de un m茅todo que se utilizar谩 la propiedad donde est谩 aplicado. Este m茅todo de validaci贸n debe ser p煤blico y est谩tico.
public static ValidationResult ValidateIBAN(object value, ValidationContext validationContext)
{
if (value == null) { throw new ArgumentNullException("value"); }
if (validationContext == null) { throw new ArgumentNullException("validationContext"); }
var userinfo = (UserInfo)validationContext.ObjectInstance;
if (!userinfo.iban.ToUpper().StartsWith(userinfo.countryCode))
{
return new ValidationResult("IBAN not valid");
}
return ValidationResult.Success;
}
El par谩metro ValidationContext nos permite acceder a la instancia del modelo para obtener el valor de otra propiedad, en este caso de la propiedad CountryCode. Si la validaci贸n es correcta, devolvemos un ValidationResult.Success y si es incorrecta, una nueva instancia de ValidationResult pasando el mensaje de validaci贸n.
Suspendiendo y reanudando el estado de las validaciones
Otro tema tan importante como el poder validar los campos de un formulario es que esas validaciones sobrevivan a una suspensi贸n y finalizaci贸n de la aplicaci贸n. Veamos como hacerlo.
Para guardar el estado, tenemos que sobrescribir el m茅todo OnNavigatedFrom del ViewModel y a帽adir el siguiente c贸digo:
public override void OnNavigatedFrom(Dictionary<string, object> viewModelState, bool suspending)
{
base.OnNavigatedFrom(viewModelState, suspending);
if (viewModelState != null)
{
AddEntityStateValue("errorsCollection", userInfo.GetAllErrors(), viewModelState);
}
}
Este m茅todo nos garantiza que cuando la aplicaci贸n se suspenda, los errores de validaci贸n se serializar谩n a disco. Por un lado, el m茅todo GetAllErrors devuelve todos los errores que contiene la clase BindableValidator y, por otro, el m茅todo AddEntityStateValue a帽ade esa colecci贸n de errores de validaci贸n en un diccionario en el sesi贸n state.
Y para restaurar el estado, sobrecribimos el m茅todo OnNavigatedTo de la siguiente forma:
public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
if (viewModelState == null) return;
base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState);
if (navigationMode == NavigationMode.Refresh)
{
var errorsCollection = RetrieveEntityStateValue<IDictionary<string, ReadOnlyCollection<string>>>("errorsCollection", viewModelState);
if (errorsCollection != null)
{
userInfo.SetAllErrors(errorsCollection);
}
}
}
En este caso, primero estamos obteniendo la colecci贸n de errores mediante RetrieveEntityStateValue y lo establecemos en el BindableValidator mediante el m茅todo SetAllErrors.
Conclusi贸n
En esta entrada hemos vistos dos aspectos importantes que tenemos que tener en cuenta para a帽adir validaciones en formulario. Por un lado, hemos visto que haciendo uso de la clase ValidatableBindableBase, los atributos de DataAnotation y el behavior HighlightFormFieldOnErrors podemos a帽adir f谩cilmente validaciones en campos y que podemos seguir utilizando el atributo CustomValidator para crear validaciones personalizas. Hemos visto tambi茅n c贸mo guardar el estado de las validaciones mediante los m茅todos de la clase ValidatableBindableBase para que se pueda restaurar si la aplicaci贸n es finalizada.