gracias (cc) Foto por woodleywonderworks

Aprovechando la reciente traducción de este sitio web al inglés, voy a dedicar este post a explicar cómo gestionar la traducción de contenidos en ASP.NET MVC multi idioma, cuando hablamos de contenido con cierto tamaño y formato, es decir, más allá de los archivos de recursos, útiles sobre todo para palabras o frases cortas.

Traducción mediante archivos de recursos

En mi caso, al iniciar la traducción, empecé utilizando archivos de Recursos (.resx), que permiten disponer de una librería de recursos organizados por clave-valor, en este caso, cadenas, donde es el propio servidor el que gestiona qué idioma mostrar.

Para ello, se dispone de un archivo “Content.resx” donde se definen todos los textos que vamos a utilizar, con su clave correspondiente:

resources1

A continuación, creamos un archivo con el mismo nombre, pero añadiéndole el sufijo del código ISO del idioma al que queremos traducir, que en el caso del inglés es “en”. Por tanto, el archivo será “Content.en.resx”, y duplicamos las claves del archivo original, pero traduciendo el contenido en este caso:

resources2

La aplicación utiliza los valores de:

para determinar el idioma a utilizar, y para ello utilizará el sufijo que hemos añadido al archivo de recursos. En caso de no encontrar ningún archivo con el sufijo correcto (“de”, por ejemplo), utilizará el archivo sin sufijo como fallback. Por eso este archivo sin sufijo siempre representa el idioma por defecto de nuestro contenido.

Las ventajas de este sistema es que al estar los recursos dentro del ensamblado, el acceso a los mismos es inmediato y es el sistema el que se encarga de decidir qué archivo de recursos utilizar en función del idioma, liberándonos de dicha tarea.

En nuestro ejemplo, ya podemos utilizar en la vista el siguiente código (si Content.resx está en el raíz de la aplicación):

que mostrará el subtítulo “Formación académica” en la tercera pestaña de la página “/es/about“, mientras que mostrará “Academic” en la página “/en/about“. En este caso, utilizo un parámetro en cada ruta “{culture}/about” que indica el idioma a seleccionar.

El código de ejemplo que gestiona el cambio de idioma sería:

Que implica que hemos definido en nuestra tabla de rutas que éstas tienen un parámetro {culture} en cada una de ellas. La gestión de rutas multi idioma la dejaremos para otra publicación.

Pero utilizar archivos de recursos tiene dos problemas fundamentales:

  • Al ser archivos compilados, es necesario volver a compilar y publicar las DLLs del site para cualquier modificación, lo que le resta versatilidad y mantenimiento.
  • Cuando necesitamos traducir grandes bloques de contenido, que a su vez incorporan formato HTML, se convierte en un formato poco manejable y que mezcla formato con contenido. Como ejemplo, si accedemos a una página con descripción de proyecto, como /es/Projects/web-personal, observamos que las secciones de “Descripción del proyecto” o “Responsabilidades y tareas” contienen gran cantidad de texto y formato.

Markdown al rescate

Para solucionarlo he separado el contenido en archivos de Markdown para editar el contenido. La sintaxis de Markdown es muy sencilla, y se hizo muy popular para la edición de Wikis, por ejemplo. Existen diversos editores para modificar archivos markdown, si no queremos tener que aprender la sintaxis. Yo estoy utilizando Markdown Pad 2.

Para diferenciar los idiomas, sigo utilizando la misma filosofía de sufijos ISO que con los archivos de recursos, añadiendo “.en” a los archivos en inglés. De esta manera, por ejemplo, para traducir el contenido de la sección “Responsabilidades y tareas” del ejemplo anterior, /es/Projects/web-personal, tenemos 2 archivos: Project12-tasks.md y Project12-tasks.en.md. De esta manera, la gestión del contenido se hace con un editor especializado, que me permite modificar el formato de manera sencilla, y los cambios son visibles con tan sólo subir el archivo afectado, sin tener que recompilar el site.

La edición de un archivo de markup es tan sencilla como abrir el archivo con el editor y realizar las modificaciones al contenido:

project12_tasks

Como se puede observar, el propio archivo de markdown permite incluir códigos HTML, aunque luego no los muestre en la vista previa. Esto es importante porque nos permite total flexibilidad con nuestro contenido y luego veremos como este código HTML insertado sí que es tenido en cuenta a la hora de generar el HTML asociado.

A modo de ejemplo, así es como ha quedado organizado el contenido asociado con el proyecto 12 completo, /es/Projects/web-personal:

project_md

Donde observamos un archivo de recursos para los textos sencillos, y dos archivos de markdown para las dos secciones complejas, y en cada caso, la correspondiente réplica en inglés.

De markdown a HTML

Obviamante, el formato markdown no se puede mostrar directamente en el navegador como tal, sino que tenemos que transformarlo al HTML correspondiente y enviarlo a la vista.

Para ello, utilizo la librería de código abierto MarkdowDeep, que nos permite transformar el contenido markdown en HTML con muy poco esfuerzo. También implementa una librería de cliente que nos permitiría editar el markdown desde el navegador, mediante javascript.

Para instalar la librería, utilizaremos Nuget. Existen dos versiones de la librería: una que sólo incluye la parte de .NET y otra que incluye además el código de cliente. En mi caso, sólo he necesitado la versión de .NET, por lo que procedemos a su instalación, desde la Consola de Nuget:

O a través del gestor de paquetes de Nuget:

markdowndeep

Una vez instalada, la manera más sencilla de utilizar es creándonos una extensión de HtmlHelper que gestione esta tarea:

Notas a destacar:

  • La función recibe como parámetro el nombre base del archivo a generar, por ejemplo “/Texts/Projects/Project12/Project12-desc”, y ella ya se encarga de añadirle el sufijo de idioma correspondiente y la extensión “.md”
  • Si no encuentra el archivo con la extensión del idioma seleccionado, utiliza como fallback el archivo por defecto sin idioma.
  • La lectura del archivo se puede almacenar en Caché, si queremos aligerar la carga de proceso, aunque según mis pruebas, y al ser archivos relativamente pequeños, la mejora es muy poca, del orden de 2ms por archivo.
  • Al instanciar la librería de Markdown con la propiedad MarkdownInHtml = true, permite que el HTML incluido en el archivo se incorpore también a la salida.
  • Finalmente, devolvemos el resultado de la generación con Raw() para que el HTML no se interprete como texto, sino como salida tal cual.
  • La línea de código:

obtiene el idioma seleccionado, en mi caso, de la ruta. Esta línea dependerá de la implementación del idioma de cada uno.

Una vez tenemos la extensión implementada, tan sólo nos queda utilizarla en nuestra vista:

Como nota adicional, he tenido problemas si la ruta del archivo no es un literal de texto, sino una cadena dinámica. En este caso, en lugar de utilizar la extensión, he tenido que utilizar directamente la clase estática para acceder al método:

Conclusiones

La combinación de archivos de recursos para textos cortos y sencillos, con archivos de texto para contenido más elaborados y con formato (en este caso mediante Markdown) nos permiten realizar una gestión sencilla de aplicaciones multi idioma, fácilmente ampliables a nuevos idiomas en un futuro y que facilitan la actualización de los contenidos en un futuro.

Seguramente hay otras soluciones y cada uno tiene sus trucos, pero esta es la que he utilizado yo en mi propia web. ¿Y tú, cómo gestionas grandes bloques de contenido multi idioma? ¡Se aceptan sugerencias!

Servidores(cc) Foto por Rob Halsell

Después del post inicial de presentación, ahora viene el rollete con los detalles técnicos de cómo he montado esta web, qué tecnologías y servicios hay detrás y demás tecnicismos frikis. ¡Avisado quedas!

La web se divide en dos aplicaciones: Páginas estáticas y el Blog.

Las páginas estáticas

Tanto la pantalla de Inicio, como la parte de Proyectos y la Biografía las he implementado mediante ASP.NET MVC 5.

Dispone de un pequeño Backend, para almacenar los datos de los Proyectos, Clientes, Imágenes, etc. para el que utilizo sencillos archivos XML que deserializo a objetos DTO (Data Transfer Object) que luego manipulo con Linq. El coste de la deserialización de estos archivos está entre 5 y 10ms, que es despreciable, y que además cacheo en memoria, por lo que no supone una penalización mayor que tenerlos en una BD relacional al uso. De hecho, es apreciablemente más rápido y barato, y el volumen de datos que manejo es relativamente pequeño.

A modo de ejemplo, esta es una muestra del archivo que almacena las categorías de proyectos:

y este, un fragmento resumido de la clase C# que lo deserializa:

Una vez dispongo del modelo de datos modelado y los archivos XML con la información controlados, desarrollo las 4 vistas principales del proyecto, utilizando Razor como View engine y Bootstrap + el template Unify para el diseño y la implementación del frontend.

El blog

Para montar el blog me decidí a utilizar WordPress, porque no tiene sentido reinventar la rueda y ponerme a programar un gestor de blogs desde cero, y porque últimamente he estado trabajando con esta plataforma por motivos profesionales y le estoy cogiendo el tranquillo, además de para seguir aprendiendo y probando cosas nuevas para futuros proyectos.

Como era importante para mi mantener una uniformidad visual, decidí utilizar el mismo tema que para el resto de la web, por lo que me tocó adaptar el tema a WordPress, ya que en origen no viene preparado para ello.

Como curiosidad, comentaré que el blog está incluido dentro de la misma solución de Visual Studio que implementa la web, en una carpeta /blog, ya que me facilita las cosas a la hora de hacer el deploy del site a Azure desde el propio IDE, además de ahorrar costes de hosting porque sólo necesito un site. Pero para que esto funcione y convivan ambas tecnologías (ASP.NET MVC 5 y PHP) hay que hacer un par de retoques, para que cada una enrute sus peticiones y no interfiera en las de la otra.

En mi caso, utilizo como alojamiento Azure Websites, por lo que mi servidor web es un IIS ejecutando PHP 5.5.

Por una parte, en el archivo Web.config común hay que incluir la regla de enrutamiento de WordPress:

Importante comprobar como sólo enrutamos las peticiones a blog/*. De esta manera, el motor de PHP ignora las peticiones que no vayan explícitamente al blog y se las deja a MVC. Igualmente es importante cambiar el nombre de la regla, de “wordpress” (por defecto) a otro (en mi caso, “blog“). Esto es debido a que es muy probable que tengamos otro archivo web.config dentro del directorio del blog que utilice los valores por defecto y esto crea un conflicto en IIS que generará un error y el blog dejará de funcionar.

Por otro lado, es importante decirle a MVC que no enrute las peticiones al blog, para que las maneje PHP. Esto se consigue añadiendo la siguiente línea antes del código de definición de las rutas de nuestra aplicación:

Los servicios

Para el alojamiento utilizo 1 Azure Website, que comparten tanto el site en MVC 5 como el blog en WordPress, así comparto gastos de hosting.

Para mejorar el rendimiento, utilizo CloudFlare como proxy DNS, que me proporciona servicios de seguridad y optimización automáticos de forma transparente, y Varnish como servidor proxy-caché, que tengo instalado en una máquina virtual Extra pequeña con Ubuntu, también sobre Azure. La unión de estos dos servicios reduce enormemente el número de peticiones que recibe el sitio web, por lo que es suficiente con una instancia compartida del Website para mantener el site.

Finalmente, una vez aplicadas las técnicas de optimización WPO al site principal, www.sergigisbert.com, obtenemos la siguiente calificación de GTMetrix:

gtmetrix webVer el informe completo

Siempre penalizada por la inclusión de recursos externos a los que no tenemos acceso.

Dudas, aclaraciones, preguntas, sugerencias, ¡aquí me tenéis!

Desde hace más de diez años, venimos desarrollando aplicaciones web utilizando tecnologías de Microsoft, por lo que hemos pasado por ASP classic, ASP.NET 2 con Web Forms y Rest Services con WCF y .NET 3.5.

Recientemente, hemos empezado a desarrollar un proyecto de tamaño considerable y con poco tiempo de desarrollo, por lo que nos planteamos la mejor tecnología a utilizar, teniendo en mente el objetivo de maximizar la reutilización de componentes de código, y la separación entre el desarrollo del backend y el frontend, para aprovechar la especialización de los miembros del equipo.

Estas premisas iniciales nos llevaron a probar el framework MVC de ASP.NET, justo recién salida la versión 3. Como con toda tecnología o framework que se utiliza por primera vez, existe una curva de aprendizaje y de adopción de buenas prácticas que tienes que asumir como inversión, si quieres que el medio plazo la productividad del desarrollo se dispare.

Después de casi 2 meses de investigaciones, pruebas varias, discusiones y marcha atrás en algunos aspectos del diseño de la aplicación, os dejo una recopilación de enlaces que nos han servido para adoptar buenas prácticas y para implementar técnicas que han mejorado, o lo harán, el desarrollo de cualquier aplicación web.

Formación MVC

Por supuesto, empezamos por el principio, la formación:

Blogs de referencia:

Desarrollando con MVC

Una vez hemos pasado el periodo inicial de formación, o si bien ya teníamos experiencia con MVC, aquí comparto una serie de herramientas que se han vuelto imprescindibles en el desarrollo del día a día y que mejoran notablemente la productividad y mantenimiento de la aplicación:

  1. T4MVC: Esta librería nos crea automáticamente clases tipadas que eliminan la necesidad de utilizar literales de cadena para acceder a nuestras vistas, controladores, acciones y recursos estáticos.

Por ejemplo, en lugar de

podemos utilizar

Lo que además significa que tenemos IntelliSense incluido, con lo que la productividad se incrementa. La lista completa de funcionalidades la podemos encontrar en http://mvccontrib.codeplex.com/wikipage?title=T4MVC_doc&referringTitle=T4MVC

  • Con T4MVC tenemos acceso a nuestros recursos con IntelliSense, pero cada vez que añadimos un nuevo elemento, tenemos que volver a ejecutar la plantilla para que genere las nuevas clases que añamos incluido. Para evitar esto y que se generen automáticamente, instalamos una extensión para Visual Studio llamada Chirpy.Este plugin (que sirve para otras cosas interesantes, que no usamos, pero que merece la pena que investiguéis) se integra directamente con T4MVC y permite que se ejecute automáticamente en cada build que hagamos. Además, permite ejecutar cualquier template T4 propio que podamos tener.

Optimizando la aplicación web MVC

Normalmente, las aplicaciones se suelen desarrollar sin tener en cuenta desde el inicio las posibles optimizaciones que se deben realizar para aumentar el rendimiento de la misma, teniendo que ir modificándola a posteriori conforme van apareciendo los problemas.

El mejor resumen de técnicas de optimización para sitios web podría ser el elaborado por Yahoo: http://developer.yahoo.com/performance/rules.html, que además viene acompañado por una utilidad que nos permitirá comprobar estas reglas en nuestro sitio web: http://developer.yahoo.com/yslow/

También es interesante echarle un ojo a PageSpeed de Google, que también nos proporciona una herramienta para mejorar el rendimiento de nuestra aplicación.

A continuación os muestro algunas librerías que utilizamos para optimizar nuestras aplicaciones y aplicar las reglas anteriores, y que se integran muy bien con el desarrollo de MVC desde el inicio, por lo que es muy recomendable tenerlas en cuenta.

  1. Utiliza CDNs para acceder a frameworks generalistas (como JQuery).

Tanto Google como Microsoft disponen de un CDN que aloja algunos de los frameworks de javascript más comunes. Se recomienda utilizarlos para liberar peticiones HTTP de nuestro servidor y porque si el usuario ha visitado otra web que también use el CDN, ya tendrá el archivo cacheado en el navegador.

  • Combres: Esta librería integra minimización, compresión, combinación y cacheo de archivos JS y CSS. Permite, por tanto, reducir el ancho de banda utilizado por nuestros archivos de script y css, y reducir las peticiones HTTP que recibe nuestro servidor. Más detalles en http://www.codeproject.com/KB/aspnet/combres2.aspx. Otra ventaja incluida indirectamente es que, al centralizar las referencias a librerías de JS y de CSS, nos permite cambiar un archivo por otro en un único punto de la aplicación (p. ej. cambiar la versión de JQuery), y no tener que modificar todas las páginas que lo referencian.

    Combres también permite utilizar recursos externos, por lo que podemos (y debemos) cargar los scripts del CDN y que Combres los minimice y combine con los nuestros propios.

  • ASP.NET Sprite and Image Optimization: Utilizar Sprites para servir las imágenes de la aplicación. Esta librería se encarga de generar automáticamente archivos de Sprites de imágenes a partir de las imágenes de nuestra aplicación, y decide, en función del navegador del usuario, cuál es la mejor manera de servir cada imagen.

Optimizaciones en el servidor IIS

A nivel de IIS, lo más importante es configurarlo correctamente  para habilitar la compresión de las respuestas y la caché en nuestra aplicación.

  1. Configuración de la compresión de la respuesta con Gzip: http://www.iis.net/ConfigReference/system.webServer/httpCompression
  2. Configuración de la caché de respuesta: http://www.iis.net/ConfigReference/system.webServer/caching