[XAML Tip] Validación de datos en Windows Phone 8 y Windows 8

Hola a todos!

Este fin de semana he estado trabajando en una forma sencilla y reutilizable de validar datos en aplicaciones Windows Phone 8 y Windows 8. Los que hayáis desarrollado aplicaciones en Silverlight o WPF, recordaréis que al establecer un Binding podíamos añadir un atributo ValidateOnException y ValidateOnDataError. Esto hacía que automáticamente el control se enmarcase en rojo cuando existía un error o si se lanzaba una excepción al establecer el valor a la propiedad de la ViewModel.

Lamentablemente en Windows Phone 8 y Windows 8 Microsoft no ha incluido estos atributos (en serio… a veces hacen cosas incomprensibles…). Aunque existían varias formas de validar los datos, ninguna era tan sencilla de usar desde XAML como las anteriores, así que me puse manos a la obra para crear un sistema de validación que nos permita informar al usuario de que existe un error en los datos. Este sistema tenía que cumplir varios requisitos.

1) En primer lugar debería permitirme definir cuando se va a llevar a cabo la validación:

  • Automáticamente al cargar el control (Evento Loaded)
  • Cuando el control pierda el foco (Evento LostFocus)
  • Cuando el usuario presione sobre el control (Evento Tap/Tapped)
  • Cuando el usuario presione una tecla o la suelte (Eventos KeyDown y KeyUp)
  • Cuando el texto del control cambie (Evento TextChanged)

2) Además, me debía permitir definir el tipo de validación a realizar:

  • El texto no está vacío.
  • El texto es un número valido.
  • El texto no es un número.
  • El texto tiene un formato determinado, usando expresiones regulares.

3) Por último, debía poder obtener el estado de la validación en mi ViewModel de alguna forma, para actuar en consecuencia.

Aunque existen algunos validadores en NuGet, ninguno cumplía todos mis requisitos. Así que cree un nuevo proyecto en NuGet y me puse manos a la obra a crear un validador para Windows Phone 8 y Windows 8. El resultado creo que ha quedado bastante bien y sobretodo es muy sencillo de usar. pongamos el caso de que deseo validar que un usuario ha introducido su nombre. La validación que quiero realizar es que el texto no esté vacío y el momento en el que deseo hacerla es cuando el control pierda el foco. No quiero que se valide nada más entrar a la página. Con el validador que he creado, podríamos hacer algo parecido a esto:

   1: <TextBox x:Name="textName">

   2:     <system:Interaction.Behaviors>

   3:         <val:TextBoxValidator ValidationFailed="{Binding NameValidationFailed, Mode=TwoWay}" 

   4:                               ValidateOnEvent="LostFocus" 

   5:                               ValidationPatternType="NotEmpty">

   6:         </val:TextBoxValidator>

   7:     </system:Interaction.Behaviors>

   8: </TextBox>

Hacemos uso de un Behavior llamado TextBoxValidator con tres propiedades:

  • ValidationFailed, esta propiedad boolean nos indicará si se ha fallado o no la validación, podemos enlazarla a una propiedad de nuestra ViewModel para obtener el estado de cada validación.
  • ValidateOnEvent, nos permite indicar en que momento deseamos que se dispare la validación, podemos escoger entre: KeyDown, KeyUp, Loaded, LostFocus, Tapped o TextChanged.
  • ValidationPatternType, indicamos que validación queremos llevar a cabo: IsNumeric, NotIsNumeric, NotEmpty o RegExPattern.

Una vez indicadas estas tres propiedades, podríamos añadir un TextBlock debajo de nuestro TextBox que muestre la validación y que solo sea visible cuando se falle la validación:

   1: <TextBlock Text="the name could not be empty." FontSize="24" Foreground="Red" Margin="12,0"

   2:            Visibility="{Binding NameValidationFailed, Converter={StaticResource BooleanToVisibilityConverter}}">

   3: </TextBlock>

El resultado de este XAML sería el siguiente:

image

Lo que mas me gusta es que no tenemos que añadir nada de lógica en code behind, todo funciona simplemente desde XAML sin que nos tengamos que preocupar de ello.

Otro caso interesante es el de validar entradas más complejas como, por ejemplo, un correo electrónico o un campo alfanumérico con un formato específico. Para esto, vimos que uno de los valores permitidos en la propiedad ValidationPatternType era RegExPattern.

Si indicamos este tipo, podremos indicar una cuarta propiedad llamada RegExPattern, en la que podremos indicar una expresión regular que el validador usará con el valor del texto. Si se encuentra al menos una coincidencia, se pasa la validación:

   1: <TextBox x:Name="textEmail">

   2:     <system:Interaction.Behaviors>

   3:         <val:TextBoxValidator ValidationFailed="{Binding EmailValidationFailed, Mode=TwoWay}" 

   4:                                 ValidateOnEvent="LostFocus" 

   5:                                 ValidationPatternType="RegExPattern" 

   6:                                 RegExPattern="^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,4})$">

   7:         </val:TextBoxValidator>

   8:     </system:Interaction.Behaviors>

   9: </TextBox>

  10: <TextBlock Text="failed!" FontSize="24" Foreground="Red" Margin="12,0"

  11:            Visibility="{Binding EmailValidationFailed, Converter={StaticResource BooleanToVisibilityConverter}}">

  12: </TextBlock>

En este caso validamos un formato de correo electrónico, de forma sencilla y directamente desde XAML.

Si queréis usar estos validadores en vuestros proyectos, los podéis descargar desde NuGet directamente. Existe una versión para Windows Phone 8 y otra para Windows 8.

El uso de ambos es completamente igual. El mayor cambio es el soporte a los Behaviors. Mientras que en Windows Phone 8 se incluyen dentro de System.Windows.Interactivity, en Windows 8 tenemos que usar la librería WinRTBehaviors de mi buen amigo y Windows Phone MVP @Localjoost.

Si queréis ver el código fuente de ambos proyectos, podéis hacerlo desde Nokia Projects: https://projects.developer.nokia.com/input_validations, incluso podréis descargar el código usando GIT.

Por último, he creado dos ejemplos de como usar estos validadores, uno para Windows Phone 8 y otro para Windows 8, que también podéis descargar desde Nokia Projects: https://projects.developer.nokia.com/input_validations/files/downloads. Y con esto termino. Espero como siempre que os sea muy útil y que lo implementéis en muchos productos (y si lo hacéis, hacédmelo saber, principalmente por ego jejeje).

Un abrazo a todos y Happy Coding!!

 

 

 

[Windows 8] Tip: Saber si tenemos acceso real a internet

Hola a todos!

Una situación muy común hoy en día es encontrarnos con una conexión WiFi o Ethernet abierta en la que, una vez conectados, debemos acceder a un portal local para indicar un usuario y password. En este caso, efectivamente tenemos conectividad pero no tenemos salida a internet. Nos encontramos ante lo que se denomina una conexión cautiva.

Normalmente, comprobamos simplemente si tenemos internet o no. Para ello manejamos el evento NetworkStatusChanged de la clase NetworkInformation y consultamos el método GetInternetConnectionProfile. Si esta llamada nos devuelve NULL, entonces no tenemos internet. Si devuelve un ConnectionProfile, tenemos internet:

   1: if (NetworkInformation.GetInternetConnectionProfile() == null)

   2: {

   3:     ShowNotification("Conexión a internet desactivada.");

   4: }

   5: else

   6: {

   7:     ShowNotification("Conexión a internet activada.");

   8: }

Esto puede ser suficiente en muchos casos pero, como comentábamos al principio del artículo, puede que tengamos conexión de internet mediante una WiFi abierta. Cuando intentemos ejecutar una petición web o abrir una página web, lo primero que obtendremos será un portal interno de la red, pidiéndonos usuario y password. Esto es cada vez más normal en eventos y en hoteles. Con nuestro método anterior, tendríamos un falso positivo. Si, tenemos una conexión a internet, pero es inútil hasta que el usuario se autentifique en el portal. ¿Como podemos solucionar esto? Mediante el uso del método GetNetworkConnectivityLevel de la clase ConnectionProfile.

GetNetworkConnectivityLevel nos devolverá un enumerado con el nivel de conexión del perfil:

None, no disponemos de conectividad.

LocalAccess, acceso solo a red local, no a internet.

ConstrainedInternetAccess, nos informa de que…. nos encontramos tras un portal de autenticación!!, justo lo que necesitamos. Pero tiene su truco… luego lo veremos.

InternetAccess, acceso a internet y a la red local.

De esta forma, podríamos modificar nuestro primer código por algo así:

   1: public NetworkConnectivityLevel GetConnectivityLevel()

   2: {

   3:     var iConnection = NetworkInformation.GetInternetConnectionProfile();

   4:     if (iConnection == null)

   5:         return NetworkConnectivityLevel.None;

   6:  

   7:     return iConnection.GetNetworkConnectivityLevel();

   8: }

   1: var stat = this.networkService.GetConnectivityLevel();

   2:  

   3: switch (stat)

   4: { 

   5:     case NetworkConnectivityLevel.None:

   6:         ShowNotification("No existe conexión a internet.");

   7:         break;

   8:     case NetworkConnectivityLevel.InternetAccess:

   9:         ShowNotification("Existe conexión a internet.");

  10:         break;

  11:     case NetworkConnectivityLevel.LocalAccess:

  12:         ShowNotification("Solo acceso local.");

  13:         break;

  14:     case NetworkConnectivityLevel.ConstrainedInternetAccess:

  15:         ShowNotification("Debe autenticarse en el portal de la red.");

  16:         break;

  17: }

Ahora ya tenemos un control más granulado sobre el estado de nuestra conexión. Pudiendo indicar al usuario el estado exacto en el que se encuentra.

Pero no podemos confiar a ciegas en GetConnectivityLevel. Puede darse el caso en el cual nos encontremos en una conexión con un portal de autenticación y el estado devuelto sea LocalAccess, pues Windows no ha podido detectar el portal. Para estos casos la recomendación es que, siempre que nos encontremos en el estado LocalAccess, intentemos hacer una petición a una web pública conocida. Por ejemplo el archivo robots.txt de www.windowsphone.com (solo un ejemplo, aquí cada uno que vea la que prefiere). Al tratarse de un archivo plano, será muy fácil saber si nos encontramos tras un portal. Si la petición falla: no tenemos acceso a internet. Si la petición nos devuelve un archivo HTML, es muy posible que nos encontremos ante un portal y podamos informar al usuario de ellos.

Tendremos que tener en cuenta el caso concreto, esta es la recomendación oficial de MSDN para detectar un portal, depende de la situación, creo que en el estado LocalAccess podríamos informar al usuario de que existe la posibilidad de que:

A) tenga un problema con la conexión

B) Necesite autenticarse en un portal, ofreciéndole la opción de que abra un navegador y lo compruebe.

En ambos casos nos ahorramos la petición extra. Pero a cambio no podemos darle una respuesta concisa al usuario. Por eso comento que, depende de nuestras necesidades, puede hacer falta que realicemos la comprobación o solo con avisar nos llegará.

¿Y Windows Phone?

Pues… este código compila perfectamente en Windows Phone 8, pero no funciona. Si intentamos acceder al método GetNetworkConnectivityLevel desde un proyecto Windows Phone 8, obtendremos una excepción NotImplementedException, de esas que tanto gustan en Redmond… ¿Le pagarán a alguien a razón de cuantas NotImplementedException incluya en el API de Windows Phone? Esperemos que en futuras versiones, se reduzca el número de estas feas excepciones…

Y… FIN!

Esto es todo amigos! Un pequeño truco, para ayudarnos a crear apps que informen lo mejor posible al usuario sobre su conexión. A continuación os dejo un ejemplo de como aislar esta comprobación en un componente WinRT para poder usarlo tanto en C# como Javascript o C++ y reutilizarlo en diferentes proyectos. A disfrutarlo!

Un saludo y Happy Coding!