Aplicaciones multilenguaje ASP.NET MVC

El desarrollo de capa de presentación Web en .NET ha cambiado desde que Microsoft se decidió a realizar una implementación oficial del conocido patrón MVC en la plataforma ASP.NET y la (amplia) comunidad de desarrolladores de ASP.NET lo asumió con todos los cambios que implica.

Y esta implementación ha calado rápido y profundo, (ya se está desarrollando la segunda versión) ha llegado a la gente acostumbrada a Frameworks ligeros de desarrollo web que incluían sus propias implementaciones de MVC. Entre otras ventajas, ha ayudado a estandarizar la organización de los archivos del sitio y refinar las responsabilidades (Controladores -> Procesos de interfaz de usuario, workflow de la aplicación… Vistas –> mostrar la información de manera amigable y recoger los datos, validaciones de formato…).

Si deseamos acogernos a la implementación de Microsoft de dicho patrón deberemos ir encajando los problemas la funcionalidad de toda la vida sobre estas nuevas reglas de juego.

Uno de los requisitos clásicos más general es que nuestra aplicación disponga de características de globalización / localización. La idea general es la de siempre, evitar incluir ninguna referencia explicita a una determinada cultura y jugar con las características de globalización que nos ofrece el .NET Framework. Pero como ya sabemos, en Web todo se complica un poquito más. Al correr sobre un protocolo sin estado como HTTP recae sobre nuestra aplicación recordar la cultura especificada por el usuario entre las distintas peticiones.

Entonces tenemos clara la misión:

Mostrar los elementos de la UI acordes a la cultura que el usuario especifique.

Suena fácil, no? Pues lo es, si somos un poco cuidadosos…

(Por cierto, os dejo al final del post el código de la solución para que os lo podáis descargar y probar.)

1 – Estableciendo las bases, recursos a los ficheros de recursos.

Vamos a ir realizando un ejemplo paso a paso para evitar perdernos:

  1. Creamos un nuevo proyecto ASP.NET MVC Web Application
  2. Incluimos en el proyecto la carpeta de App_GlobalResources
  3. Incluimos un par de ficheros de recursos de acuerdo a la configuración de recursos por idioma que deseamos incluir. 

    La convención de nomenclatura de los mismos dice que la primera parte del archivo es el nombre base. La segunda especifica la cultura (puede también ser solo el lenguaje que la forma). Esta última parte es opcional, entendiéndose como el juego de recursos predeterminado en caso de no exista.

    Es responsabilidad del .NET Framework enlazar automáticamente (en base a los ficheros de recursos especificados) el fichero adecuado con a la cultura especificada en actual hilo de ejecución.

    Por ejemplo, creamos el fichero predeterminado (en Inglés) y otro para español sin tener en cuenta el país. (MLTest.resx y MLTest.es.resx  (para cualquier cultura con lenguaje español)) 

  4. Incluimos en el conjunto de ficheros de recursos todos los recursos que nuestra aplicación va a necesitar. 
    En nuestro caso solo uno con nombre “GREETING“. 
  5. Incluimos desde una vista ( por ejemplo Views/Home/Index.aspx) la forma de consumir el recurso:

    <%
    =Resources.MLTest.GREETING%>

    Fuertemente tipado, Intellisense, detección de errores en tiempo de compilación…(da gusto verlo!!)

Bueno, pues ya tenernos una aplicación que nos muestra los recursos en base a la cultura del Framework instalado (en el servidor Web en este caso) que se encarga de especificar la cultura del hilo de ejecución.

Ya es algo, pero no es lo que queremos, verdad?

(El que dude que revise la misión que hemos puesto arriba)

2- Estableciendo la cultura del hilo en ejecución

Ya sabemos que debemos establecer la cultura del hilo de ejecución para que el Framework cargue el fichero de recursos adecuado, pero ahora la pregunta es cuando?

Recordar que cada petición empieza de nuevo y no recuerda las anteriores ( HTTP es stateless). Indagando un poco en la MSDN podemos ver que la clase Controller (de la que derivan nuestros propios controllers) de la implementación del MVC de Microsoft especifica dos métodos virtuales que nos van a resultar muy interesantes:

  • OnActionExecuting
    Se ejecuta antes de la llamada a cualquier Action Method.
  • OnActionExecuted
    Se ejecuta después de la llamada a cualquier Action Method. (obvio, no?)

Como debemos de establecer la cultura cada vez que nos realicen una llamada a cualquier action method de cualquier controlador, lo ideal es crear un controlador base del cual hereden todos los demás y sobrescribir en el método OnActionExecuting el mecanismo para restablecer la cultura. Nuestros controladores harán así transparente al resto de la aplicación las particularidades de trabajar con estado. También podríamos usar un atributo aplicado sobre la clase base y en la implementación del atributo especificar que implementa el interfaz IActionFilter si lo queremos hacer un poco mas AOP que OO.

En la implementación que os dejo utilizo un atributo con el que decoro la clase base (Al gusto!!) 

En el ejemplo:

La clase base de los controladores:

[SetCulture] 
public class BaseController : Controller 
{ 
... 
}

Y en la implementación de la clase del atributo programo como establecer la cultura seleccionada en el hilo activo:

public class SetCultureAttribute : FilterAttribute, IActionFilter 
{ 
	public void OnActionExecuting(ActionExecutingContext 
actionContext) { CultureHelper.SetCulture(GetCurrentCulture
(actionContext), actionContext.
HttpContext.Session) } ...

Para almacenar la cultura especificada entre las llamadas podemos emplear diferentes mecanismos, en este ejemplo el flujo de trabajo será el siguiente:

  1. Revisar si tiene alguna cookie de nuestro sitio que especifique la cultura.
  2. Comprobar la variable de sessión de la cultura.
  3. En caso de no disponer de ninguna de las dos informaciones anteriores, preguntar por las culturas configurados en el navegador y presentar la interfaz de usuario en base a la primera coincidencia que se produzca.

En la implementación que os dejo del proyecto podéis consultar los métodos que he creado para realizar dichas acciones, son muy sencillos y podéis modificarlos al gusto.

Los principales métodos son:

  • GetCurrentCulture de la implementación del atributo, que consulta la información previamente almacenada en base al flujo que hemos comentado.
  • CultureHelper.SetCulture que recibe la cultura a asignar y el objeto intrínseco Session Object y carga cultura en el hilo actual de ejecución.

Aquí nos aparece otro problema.(Que raro, eh?)

Si el usuario es la primera vez que accede a la Web no tiene cookie ni session establecida. 

Para intentar que la aplicación sea lo más “inteligente” posible, recogemos del navegador las culturas que tiene especificadas y las cruzamos con una sección especifica del fichero de configuración creada a tal efecto.

Por ejemplo, si el usuario tiene Chino e italiano necesito determinar si mi aplicación dispone un algún fichero de recursos que da soporte a alguno de los mismos. Para ello me creo una section en el fichero de configuración y un handler que lo soporte que especifique una lista de culturas soportadas y su orden de aplicación. (Así vemos también como poder realizar esta tarea.)

Observar que la tarea no es tan sencilla como coger el idioma y asignarlo al hilo y dejar que el Framework resuelva. (si no lo puede resolver que aplique la predeterminada) puesto que si el usuario tiene, entre todos los lenguajes del navegador, uno que tenemos implementado nos lo estaríamos saltando. (Por ejemplo Chino, Italiano, Español, Ruso)

En el fichero de configuración especificamos:

<
configSections>

<section name=“EnabledLanguages” type=“AndoniArroyo.MLTest.
Controllers.Infrastructure.Configuration.EnabledLanguagesSection,
AndoniArroyo.MLTest”
/>

</
configSections>

además incuimos la citada sección:

<!–Especifica el conjunto de culturas con el que trabaja la aplicación. La última es la cultura por defecto–>
<EnabledLanguages>
<Languages>
<add title=“Spanish” code=“es-ES” />
<add title=“English” code=“en-GB” />
</Languages>
</EnabledLanguages>

Para que la aplicación pueda manejar esta section debemos implementar el handler que la maneje:

namespace AndoniArroyo.MLTest.Controllers.Infrastructure.
Configuration { public sealed class EnabledLanguagesSection : ConfigurationSection { [ConfigurationProperty("Languages", IsDefaultCollection = true,
IsRequired = true)] internal LanguageElementCollection EnabledLanguages { get { return (LanguageElementCollection)this["Languages"]; } } } }

Estos lenguajes será los que tendrá en cuenta el método que intenta cargar el lenguaje desde la información recogida del navegador.

Pues ya casi lo tenemos.

Nos queda por ver como el usuario puede explicitamente modificar la cultura con la que desea trabajar en la aplicación. Visto todos lo anterior la cosa es fácil, no? Basta con crear un action method de modificación de la cultura y en la implementación del mismo llamar explícitamente al método de establecer cultura con el parámetro recibido. (CultureHelper.SetCulture pasándole la cultura especificada por el usuario y la referencia al objeto de sesión…)

Recapitulando, hemos visto unas cuantas cosas:

  • Hemos observado los mecanismos de globalización que nos da el .NET Framework y como extenderlos para nuestras necesidades en web.
  • Definido una política de establecer la cultura activa de la aplicación intentando que la aplicación se adapte al usuario.
  • El punto donde cubrir las características de sin estado del HTTP para establecer la cultura especificada por el usuario.
  • Hemos creado secciones personalizadas en el fichero de configuración y hemos visto como manejarlas.

Y como extra lo prometido el ejemplo con todas estas ideas en acción.

5 comentarios en “Aplicaciones multilenguaje ASP.NET MVC”

  1. Me gustó el post … pero no puedo dejar de hacerte una pregunta; ¿porqué tomas al último valor como el valor por defecto? () … 😀

    Simple duda

  2. Para asegurarnos que se establece la cultura deseada independientemente del Framework instalado en el servidor y sin tener que recompilar la aplicación.

    Imagina que tenemos un fichero de recursos para es-ES y el Framework del servidor está en es-ES pero queremos que la cultura predeterminada de nuestra aplicación sea en-GB. Si no cambiamos nada, en el hilo se cargarán los recursos de es-ES que es un comportamiento que no deseamos.

    Con esta cultura predeterminada cubrimos ese escenario.

  3. Andoni buenas, no entendiste mi pregunta (porque no me expresé bien). ¿porqué el último es el que define el default y no el primero?, por lo general suele ser el 1ro el default, por eso me llamó la atención lo del último 😀

    Salu2

  4. Ah, canalla me tomabas el pelo…!!

    Pues supongo que será por deformación profesional de lo grabado a fuego que tengo el switch con su default…

  5. no sabía que hubiese tenido tanto éxito la implementación MVC de Microsoft para ASP.NET

    Volviendo al tema del multilenguaje, me parece muy interesante el artículo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *