Mostrando entradas con la etiqueta ASP.NET Core. Mostrar todas las entradas
Mostrando entradas con la etiqueta ASP.NET Core. Mostrar todas las entradas

lunes, 9 de octubre de 2017

ASP.NET CORE–Validadores

Una de las características que tenemos disponible en aplicaciones ASP.NET desde hace más tiempo del que puedo recordar es el uso de validadores (Validators) para automatizar, en mayor o menor medida, las validaciones que siempre tenemos que realizar cuando enviamos información de un formulario al servidor.

Con la llegada de JQuery y sus extensiones JQuery.Validation y JQuery.Validation.Unobtrusive, la funcionalidad que proporcionan estos validadores mejoró en sus capacidades, ya que de manera automática (hasta donde yo sé) son capaces de realizar las validaciones en cliente antes de hacer el envío o post al servidor, haciendo que el rendimiento de la aplicación web no se resienta.

Reconozco que a mí no me habían gustado nunca, ya que les veía poca funcionalidad, poca flexibilidad y… bueno, que no me convencían. He decidido darles otra oportunidad en el ámbito de ASP.NET Core, porque lo cierto es que al final es muy probable que nos ahorren tiempo.

Primero quiero empezar con una captura de los validadores estándar que podemos encontrar en una aplicación ASP.Net Core:

image

Bastante feo, ¿no?

¿Cómo funcionan?

En el post sobre localización [acceso] ya estuvimos hablando sobre los DataAnnotations así que aquí no trabajaremos con localización, de momento una pequeña explicación de cómo funciona el sistema de validadores en ASP.NET Core.

Modelo:

En el modelo especificamos qué condiciones tiene que cumplir la propiedad para ser válida:

image

En el ejemplo anterior estamos indicando que la propiedad Email es obligatoria y que tiene que ser una dirección de correo ‘bien formada’. Estos atributos (normalmente en el namespace System.ComponentModel.DataAnnotations) se encargan automáticamente de realizar validaciones sobre los valores introducidos en la vista. Cuando el envío llega al controlador, la propiedad IsValid del ModelState indicará si los valores son correctos o no:

image

Aquí podemos ver que si el modelo no es valido, directamente vuelve a cargar la página, en la que veremos los validadores aplicados como se ven en la primera captura.

He dicho antes que los validadores se ejecutan en el cliente, pero en esta pequeña explicación sólo he hablado de la parte de servidor, lo he hecho así para poder hablar de la propiedad IsValid ya que nos puede servir también para operaciones avanzadas con el modelo una vez que estamos en la parte del servidor.

Cliente:

Para poder ejecutar esos validadores en el cliente tenemos que utilizar las librerías de Javascript JQuery.Validation y JQuery.Validation.Unobtrusive, que por suerte tenemos ya disponibles cuando creamos un proyecto de ASP.NET Core en Visual Studio:

image

Además, si entramos a la vista donde utilizamos los validadores, veremos al final de la misma como los scripts necesarios se incluyen mediante una vista parcial:

image

Esto es interesante ya que si queremos incluir nuestros propios scripts de validación, si añadimos la referencia en esta vista parcial, los tendremos disponibles automáticamente en el resto de vistas donde se utilicen los validadores estándar.

Para poder utilizar los validadores tenemos que añadirlos en la vista (tiene sentido ¿no?)

image

Como podemos ver, tenemos varios atributos asp-xxx que gestionan la validación:

  • asp-validation-summary define una sección con todos los errores encontrados.
  • asp-validation-for especifica una sección donde se mostrarán los errores encontrados para la propiedad del modelo indicada (en este ejemplo Email)

¿Cómo podemos saber que la validación funciona sólo en cliente?

Esta es fácil, basta con poner un punto de ruptura en la acción del controlador y ejecutar el proyecto, si ponemos valores incorrrectos en el formulario y hacemos el Post, veremos que NO llega ninguna petición al controlador, porque no llegaremos al punto de ruptura. De hecho, si alguna vez llegáis al servidor sin que se hayan evaluado las validaciones de cliente, tenéis un problema.

¿Esto es todo?

hasta ahora, todo lo que hemos visto es el comportamiento estándar de los validadores, pero siguen quedando como ‘feuchos’. Gracias a Bootstrap podemos hacer alguna adaptación que quede un poco más elegante, algo como esto (con iconos en las cajas):

image

Peeero, si queremos algo más avanzado, como mostrar los errores en una ventana modal (algo bastante habitual por otra parte) es cuando empiezan nuestros problemas.

Al turrón

Lo primero que hacemos es crear una vista parcial en la que ponemos el código del ‘esqueleto’ de la ventana modal que queremos mostrar:

image

A continuación incluimos esta vista parcial en la vista deseada (podríamos añadirla también en el _Layout para tenerla disponible en todas las vistas de la aplicación si nos interesa)

image

Lo siguiente que tenemos que hacer es preparar un script para que ‘capture’ el submit del formulario a validar y si hay errores muestre una ventana modal. El script en cuestión podría ser algo como esto (se trata de un ejemplo, no seáis muy duros):

image

En esta función lo que hacemos es codificar el submit del formulario, lanzamos las validaciones que tenga configuradas, y si hay algún error en esas validaciones, mostramos la ventana modal evitando que se continúe con el envío del formulario. En este ejemplo nos quedamos sólo con el último error detectado, pero creo que como ejemplo es suficiente.

Por supuesto este script tendremos que añadirlo en la página, si no, pues no se iba a ejecutar…

Si a continuación probamos el funcionamiento, tendremos esto:

image

Como podéis ver en la captura se siguen mostrando los errores de los validadores por defecto, si queremos evitarlo basta con quitar los validadores de la vista, ya que en las pruebas realizadas parece que no es necesario que estén físicamente en la página para que se realice la validación.

Conclusión

Me da la sensación de que interceptar las validaciones que se realizan por defecto puede acabar dándonos algún problemilla, pero lo cierto es que si queremos hacer algo diferente a los mensajes por defecto esta sería la aproximación que yo recomendaría.

martes, 3 de octubre de 2017

ASP.NET CORE–Localización de aplicaciones

Una de las mayores ventajas que nos proporciona la plataforma ASP.NET Core es la facilidad con la que podemos acceder a ‘las tripas’ de las llamadas que se realizan, así como el soporte por defecto de inyección de dependencias.
Combinando estas y otras características, tenemos una sustancial mejora a la hora de gestionar comportamientos como el del multiidioma, localización y globalización de aplicaciones.

Introducción

La localización en ASP.NET Core es bastante similar a como funciona en ASP.NET 4.X. Por defecto definimos una serie de archivos de recursos .resx, uno para cada cultura que soportamos. Entonces podemos referenciar los recursos por clave y, dependiendo de la cultura actual, se obtiene el valor apropiado del archivo de recursos que corresponda.
Mientras el concepto de un archivo .resx por cultura se mantiene en ASP.NET Core, la manera en la que los recursos se utilizan ha cambiado significativamente. En la versión anterior, cuando añadíamos un archivo .resx a nuestra solución, se generaba automáticamente un archivo “designer” que proporcionaba acceso a los recursos de una manera directa (static strongly typed access) mediante llamadas del tipo “Resources.MyTitleString”.
En ASP.NET Core, los recursos se acceden a través de dos abstracciones, IStringLocalizer y IStringLocalizer<T>, que se ‘preparan’ cuando son necesarias mediante la “inyección de dependencias”. Estos interfaces proporcionan un indexador que permite acceder a los recursos por clave. Si no existe un recurso para la clave especificada, el sistema utiliza la propia clave como valor del recurso.
La idea detrás de esta nueva aproximación es permitir que la aplicación se diseñe desde el principio para ser “localizable” sin que sea necesario crear todos los archivos de recursos al inicio. Así podemos simplemente utilizar los valores por defecto como claves y añadir los distintos archivos de recursos a posteriori.
Indudablemente se trata de una opción muy interesante, pero lógicamente tiene un problema relativamente serio, ya que al no tener acceso ‘tipado’ a los recursos se puede dar el caso de que sin querer cambiemos uno de esos literales / claves y entonces perdemos el vínculo con su recurso. Entiendo que es muy probable que al final cojamos costumbre de trabajar de esta manera, aunque me da la sensación de que también es muy posible que tengamos ‘dificultades’ para detectar en un momento dado qué tenemos traducido y qué no… Lo mejor entiendo que será aprovechar las ventajas de la nueva gestión de la localización, pero trabajando desde el principio con los archivos de recursos necesarios en la aplicación.

Al turrón

Todo esto es muy bonito (o lo parece) pero, ¿cómo se hace?

Añadir localización a una aplicación

El primer paso es añadir los servicios de localización a nuestra aplicación. En este caso, como estamos trabajando con una aplicación MVC, configuraremos también localización para las Vistas e incluso para los “DataAnnotations”. En ASP.NET Core añadimos estos servicios en el método “ConfigureServices” de la clase “Startup
image
En la captura anterior podemos observar cómo añadimos los servicios de localización para los tres elementos que hemos comentado; como punto interesante indicar que al especificar un “ResourcesPath” le estamos diciendo al motor dónde se ubicarán los archivos de recursos (en este caso en una carpeta “Resources”.
Al añadir estos servicios tenemos la posibilidad de inyectar las abstracciones que hemos comentado en la parte de la introducción, así como una especialización de ellas que permite trabajar con vistas “IViewLocalizer”.
Con estas instrucciones ya podríamos utilizar localización en nuestra aplicación, pero el sistema nos queda cojo ya que no hemos hecho nada para que las peticiones a la aplicación utilicen la cultura…
Para manipular estas peticiones configuraremos el middleware “RequestLocalizationMiddleware”. Este middleware utiliza una serie de proveedores para determinar la cultura actual, estos proveedores de definen en el método “ConfigureServices” de la clase “Startup
image
En la captura anterior tenemos un ejemplo de cómo generar una lista con las culturas que ‘soporta’ la aplicación y aplicar una cultura por defecto. Una vez aplicada esta configuración tenemos que añadir el middleware de localización, que se hace en el método “Configure” de la clase “Startup”.
image
Lo que hacemos en este método es obtener el servicio que hemos registrado anteriormente y lo utilizamos para indicar que lo queremos utilizar para la localización de las peticiones.

Utilizar localización en controladores y servicios

Cuando necesitemos acceder a texto localizado en servicios o controladores, lo primero que tendremos que hacer es inyectar In “IStringLocalizer<T>” en la clase y utilizar el indexador proporcionado para obtener el valor del texto:
image
Como ya hemos explicado, en el constructor del controlador inyectamos la dependencia para el “localizer” y en la “acción” Index accedemos al recurso “Título de la aplicación en castellano”. Si ahora accedemos a la página de inicio de la aplicación, veremos que aparece el texto que hemos utilizado como ‘índice’ (como hemos comentado antes, si no existe una entrada con la clave indicada, dicha clave se utiliza como texto ‘de vuelta’:
image
Ahora sería buen momento para añadir el archivo de recursos para un idioma adicional… Si partimos del hecho de que el idioma por defecto de nuestra aplicación será el castellano y viendo cómo funciona la localización en ASP.NET Core, podríamos asumir que NO necesitamos archivo de recursos para castellano, por lo que directamente crearemos el archivo de recursos para el resto de culturas configuradas, por ejemplo como se ve en las capturas anteriores, inglés.
En la parte de configuración hemos indicado que los archivos de recursos van en una carpeta “Resources”, que ASP.NET Core intenta encontrar en el raíz de nuestra aplicación, con lo que tendremos que crear dicha carpeta. a continuación, el sistema de ‘localización’ buscará el archivo de recursos correspondiente a la clase en la que estamos (HomeController), para ello por convención buscará una carpeta “Controllers” y dentro de ella el archivo de recursos (HomeController.en.resx), o también puede buscar un archivo de recurso con un nombre según este patrón: Controllers.HomeController.en.resx. Yo recomiendo la primera estructura de carpetas, lo que nos lleva a esta captura:
image
En esta carpeta crearemos el archivo de recursos (HomeController.en.resx) y en él añadiremos la entrada correspondiente a la clave que hemos utilizado anteriormente:
image
Como veréis, Visual Studio se “queja” de la clave que hemos utilizado, incluso es posible que aparezca un error en el panel de “Error List” pero por lo visto se trata de un problema del propio Visual Studio y no afecta a la compilación del proyecto. Sí, es un inconveniente…
image

Utilizar localización en Vistas

En el caso de las vistas, en lugar de ‘inyectar’ un “IStringLocalizer<T>” haremos lo propio con un “IViewLocalizer”. Este componente utiliza un patrón idéntico al que hemos visto en el caso anterior para encontrar los archivos de recursos, pero utilizando la estructura de vistas existente. por ejemplo para crear un archivo de recursos para una vista “Index.cshtml” que está en una carpeta “Home” tendríamos la siguiente estructura de carpetas:
image
Dentro de esta carpeta crearemos un archivo de recursos con el mismo nombre que la vista, como por ejemplo Index.en.resx:
image
La inyección del “IViewLocalizer” se realiza en la vista con la instrucción @inject”:
image
Como podemos ver en la captura, necesitamos importar el namespace para el interfaz, y con la instrucción @inject” generamos una propiedad “Localizer” mediante la cual accedemos al recurso.
image

Utilizar localización en ViewModels

Otro de los puntos que normalmente queremos localizar serán literales de posibles mensajes de error o advertencia que mostramos en las clases del “ViewModel”, por ejemplo a través de atributos de “DataAnnotation”. El mecanismo es análogo a los que hemos explicado anteriormente, el archivo de recursos estará en una subcarpeta de este estilo: “Resources/ViewModels/HomeViewModel.en.resx” y el uso en atributos iría así:
image
En el ejemplo vemos qué claves queremos localizar, el sistema los mapeará de manera automática.
image

Permitir a los usuarios elegir una cultura

Hasta ahora hemos estado hablando de localizar una aplicación ASP.NET Core, pero todo esto no sirve de nada si no disponemos de un mecanismo que permita al usuario cambiar el idioma de la aplicación… Hombre, sí que sirve si lo que queremos es basarnos en el idioma del navegador, pero ya que tenemos una aplicación multiidioma, pues vamos a aprovechar, ¿no?
El middleware que hemos configurado para activar la localización (recuerda, el RequestLocalizationMiddleware) ofrece un mecanismo extensible de proveedores de ‘cultura’ y además viene con tres proveedores por defecto:
  • QueryStringRequestCultureProvider: Especificar la cultura en la QueryString, por ejemplo ?culture=en
  • AcceptLanguageHeaderRequestCultureProvider: Especificar la cultura en el encabezado de la petición (mediante Accept-Language)
  • CookieRequestCultureProvider: Especificar la cultura mediante una Cookie.
En esta entrada vamos a probar el tercer proveedor, ya que parece que tiene más sentido utilizar cookies para este tema, ¿verdad?
Para permitir al usuario cambiar de idioma vamos a crear el equivalente a un control de usuario ascx en ASP.NET Core (o así lo entiendo yo) así que utilizaremos una vista parcial, _SelectLanguagePartial.cshtml en la carpeta Shared de las vistas:
image
En la captura anterior hay mucha tela que cortar, pero por intentar resumir un poco los pasos, tendríamos:
  1. Inyectamos el objeto para la localización (IViewLocalizer)
  2. Inyectamos el objeto donde hemos almacenado las opciones de la localización (IOptions<RequestLocalizationOptions>)
  3. Obtenemos la cultura de la petición, así como las distintas culturas que tenemos disponibles, estas últimas en una lista de objetos SelectListItem para poder utilizarlas en un combo con el que el usuario podrá cambiar el idioma de la aplicación (toda la sección HTML de la parte inferior)
Una vez diseñada la vista parcial tenemos que añadirla en las páginas deseadas (normalmente querremos el cambio de idioma en todas las páginas, así que podemos utilizar la vista _Layout.cshtml), para añadirla utilizaremos el método HTML.PartialAsync:
image
Para terminar, necesitamos implementar el código de controlador que realizará el cambio de idioma, acción “SetLanguage” del controlador “HomeController” como se puede ver en la captura del control de cambio de idioma.
image
En este código podemos ver cómo añadimos la cookie por defecto para el idioma en la colección de la respuesta y como (en este ejemplo) establecer un año de validez a la cookie.
¿Cómo queda el control de cambio de usuario?
Pues en esta ocasión quedaría así:
image
Si cambiamos el idioma en el combo y hacemos clic en “Guardar”, podemos ver cómo los literales se cogen de los distintos archivos de recursos.

Conclusiones

A ver, es mucho más sencillo ‘localizar’ aplicaciones en ASP.NET Core, pero, no por ser más sencillo estamos ‘aprueba’ de errores, algunos de los más típicos:
  1. Clave del recurso incorrecta (o modificada): Como este sistema no requiere que los archivos de recursos estén generados, y no tenemos tipado fuerte, se puede dar el caso de que al corregir, por ejemplo, una falta ortográfica en uno de los textos, se nos olvide modificar el texto también en los archivos de recursos… Resultado, no hay traducción.
  2. Ruta de los archivos de recursos: Por convención, el acceso a los archivos de recursos se realiza en función del Namespace de las clases donde vamos a utilizarlos, es un poco raro, pero si en algún momento veis que no está cogiendo traducciones, revisad por favor la ruta del archivo de recursos (y por supuesto como he puesto en el punto anterior, el nombre de la clave)
Creo que todavía tiene margen de mejora, pero lo cierto es que olvidarnos de establecer el CurrentCulture, la Cookie y demás, resulta ummm, cómodo.

Nota final

Si alguien se lo está preguntando, no he hecho ninguna prueba con el comportamiento de este mecanismo con controles tipo “Kendo”… No me chilléis, por favor.