Accueil Nos publications Blog Globalisation d’applications en ASP.net MVC 3

Globalisation d’applications en ASP.net MVC 3

La création d’applications multi-langues en ASP.net est rendue assez simple par l’utilisation des fichiers de ressources. Cependant en ASP.net MVC3, il n’existe aucune solution permettant d’utiliser la souplesse du modèle MVC.
A travers ce court article, je vous propose de mettre en œuvre une technique que je considère comme simple et rapide : simple car elle s’appuie sur l’implémentation du modèle MVC d’ASP.net et rapide car elle ne nécessite pas la refonte complète de votre application.
Pour commencer, il faut définir un moyen simple de choisir la langue à afficher, pour cela, j’ai choisi d’utiliser les informations de la QueryString et d’ajouter un paramètre à la Route. J’ai donc modifié la Route par défaut afin d’obtenir ceci :
routes.MapRoute(
"Default",
"{lang}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional, lang = "fr-FR" }
);
Ici, la langue est définie par défaut à « fr-FR », mais j’aurais pu mettre « en-US » ou bien tout autre choix. Idéalement, je vous conseille de mettre cette valeur dans le web.config.
Ainsi pour obtenir les différentes langues d’une page, il faudra procéder ainsi :
  • https://monsite/fr-FR/Controller/Action/ID pour le français
    (ou https://monsite/Controller/Action/ID puisque fr-FR est la valeur par défaut)
  • https://monsite/en-US/Controller/Action/ID pour l’anglais US
  • https://monsite/de-DE/Controller/Action/ID pour l’allemand
Cependant ce simple changement dans les paramètres de la Route n’est pas suffisant, il va falloir changer la culture du thread courant en fonction de la valeur renseignée.
Pour cela, il est nécessaire d’appliquer un filtre que nous nommerons LocalizeAttribute sur toutes les actions. Afin qu’il soit appliqué automatiquement, il suffit de l’enregistrer via la méthode RegisterGlobalFilters du Global.asax, comme ci-dessous.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LocalizeAttribute());
filters.Add(new HandleErrorAttribute());
}
L’implémentation de notre filtre nécessite d’hériter de la classe ActionFilterAttribute et d’en surcharger 1 méthode : OnResultExecuting. Celle-ci est appelée juste avant que le résultat de l’action ne s’exécute. Cette méthode va permettre de modifier la culture du thread courant.
public class LocalizeAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string cultureName = (string)filterContext.RouteData.Values["lang"];
// Change la culture du thread courant
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
base.OnResultExecuting(filterContext);
}
}
Maintenant que notre thread utilise la culture souhaitée, il faut que les informations qui s’affichent dans les formulaires soient correctes.
L’une des solutions les plus appréciée en .net pour la globalisation d’application est qu’il suffit de créer un fichier de ressources par culture supportée. Par exemple, dans la capture suivante, je considère l’anglais (US) comme culture par défaut et je crée un fichier supplémentaire pour le français.
Puis il faut maintenant entrer un peu plus dans les entrailles d’ASP.net MVC 3 et ajouter de nouvelles méthodes d’extensions à la classe HtmlHelper, nous allons commencer par réécrire les méthodes Label, LabelForModel et LabelFor<>. La raison est simple : simplifier au maximum !
public static MvcHtmlString GlobalizedLabel(this HtmlHelper html, string expression, Type resourceType,
string resourceKey = null)
{
var text = ResourceHelper.GetStringFromResource(resourceKey, resourceType);
return System.Web.Mvc.Html.LabelExtensions.Label(html, expression, text);
}
Cette première méthode va rechercher le texte correspondant à la resourceKey dans la ressource précisée (resourceType) à l’aide de la méthode GetStringFromResource de la classe statique ResourceHelper, celle-ci étant définie de cette façon :
public static string GetStringFromResource(string resourceKey, Type resourceType) {
if (resourceKey == null) {
return "### ERROR : resourceKey must be not null ! ###";
}
var rm = new ResourceManager(resourceType);
return rm.GetString(resourceKey) ?? string.Format("### No resource for \"{0}\" key ###", resourceKey);
}
Et puis, une fois le libellé localisé récupéré, on utilise la méthode Label du framework, histoire de ne pas réécrire la roue !
Pour la méthode suivante, pas véritablement de changement :
public static MvcHtmlString GlobalizedLabelForModel(this HtmlHelper html, Type resourceType, string resourceKey = null) {
var text = ResourceHelper.GetStringFromResource(resourceKey, resourceType);
return System.Web.Mvc.Html.LabelExtensions.LabelForModel(html, text);
}
La différence vient de l’appel à LabelForModel.
Enfin, il nous reste la méthode LabelFor<> : la différence avec cette méthode est qu’elle ajoute automatiquement le texte qui est contenu dans l’attribut Display de la propriété du modèle. Il va falloir contourner ce comportement pour aller chercher dans les ressources le libellé correspondant, on va donc se servir du nom du modèle et de la propriété pour former une clé. Par exemple, la propriété UserName du modèle LogOn aura comme clé LogOn_UserName.
La méthode correspondante sera donc écrite ainsi :
public static MvcHtmlString GlobalizedLabelFor<tmodel, tvalue="">(this HtmlHelper html,
Expression<func<tmodel, tvalue="">> expression, Type resourceType, string resourceKey = null) {
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var text = ResourceHelper.GetStringFromResource(
resourceKey ?? string.Concat(html.ViewData.ModelMetaData.ModelType.Name, "_" , htmlFieldName),
resourceType);
return System.Web.Mvc.Html.LabelExtensions.LabelFor(html, expression, text);
}</func<tmodel,></tmodel,>
Explications : après avoir recherché la propriété du modèle qu’il faut traduire, je construis la clé à partir du nom du modèle et le nom de la propriété, cette clé est ensuite envoyée à la méthode GetStringFromResource. Le résultat est transmis à la méthode LabelFor<> du framework.
Voici quelques exemples d’utilisations :
@Html.GlobalizedLabel("RememberMe", typeof(Test_MVC3.Languages.Localization), "LogOnModel_RememberMe")
@Html.GlobalizedLabelForModel(typeof(Test_MVC3.Languages.Localization), "LogOnModel_RememberMe")
@Html.GlobalizedLabelFor(m=>m.RememberMe, typeof(Test_MVC3.Languages.Localization))
Conclusion :
C’est une approche simplissime de la globalisation avec ASP.net MVC3, certains points restent à améliorer, par exemple : utiliser des paramètres dans le web.config pour fixer la langue par défaut, un fichier Resources par défaut pour éviter d’avoir à le ressaisir à chaque ligne, choisir la culture correspondant à la culture par défaut du navigateur…