Définir la culture dans une application ASP.Net MVC

85

Quel est le meilleur endroit pour définir la culture / culture de l'interface utilisateur dans une application ASP.net MVC

Actuellement, j'ai une classe CultureController qui ressemble à ceci:

public class CultureController : Controller
{
    public ActionResult SetSpanishCulture()
    {
        HttpContext.Session["culture"] = "es-ES";
        return RedirectToAction("Index", "Home");
    }

    public ActionResult SetFrenchCulture()
    {
        HttpContext.Session["culture"] = "fr-FR";
        return RedirectToAction("Index", "Home");
    }
}

et un lien hypertexte pour chaque langue sur la page d'accueil avec un lien tel que celui-ci:

<li><%= Html.ActionLink("French", "SetFrenchCulture", "Culture")%></li>
<li><%= Html.ActionLink("Spanish", "SetSpanishCulture", "Culture")%></li>

qui fonctionne bien mais je pense qu'il y a une manière plus appropriée de faire ceci.

Je lis la culture en utilisant le ActionFilter suivant http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx . Je suis un peu un MVC noob, donc je ne suis pas sûr que je mets cela au bon endroit. Je ne veux pas le faire au niveau de web.config, cela doit être basé sur le choix d'un utilisateur. Je ne veux pas non plus vérifier leurs en-têtes http pour obtenir la culture à partir des paramètres de leur navigateur.

Éditer:

Juste pour être clair - je n'essaye pas de décider d'utiliser la session ou non. Je suis content de ça. Ce que j'essaie de déterminer, c'est s'il est préférable de le faire dans un contrôleur de culture qui a une méthode d'action pour chaque culture à définir, ou existe-t-il un meilleur endroit dans le pipeline MVC pour le faire?

ChrisCa
la source
L'utilisation de l'état de session pour sélectionner la culture utilisateur n'est pas un bon choix. Le meilleur moyen est d' inclure la culture dans le cadre de l'URL , ce qui facilite le «swap» de la page actuelle avec une autre culture.
NightOwl888

Réponses:

114

J'utilise cette méthode de localisation et ajouté un paramètre d'itinéraire qui définit la culture et la langue chaque fois qu'un utilisateur visite example.com/xx-xx/

Exemple:

routes.MapRoute("DefaultLocalized",
            "{language}-{culture}/{controller}/{action}/{id}",
            new
            {
                controller = "Home",
                action = "Index",
                id = "",
                language = "nl",
                culture = "NL"
            });

J'ai un filtre qui fait le réglage de la culture / langue réelle:

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

public class InternationalizationAttribute : ActionFilterAttribute {

    public override void OnActionExecuting(ActionExecutingContext filterContext) {

        string language = (string)filterContext.RouteData.Values["language"] ?? "nl";
        string culture = (string)filterContext.RouteData.Values["culture"] ?? "NL";

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));

    }
}

Pour activer l'attribut Internationalisation, ajoutez-le simplement à votre classe:

[Internationalization]
public class HomeController : Controller {
...

Désormais, chaque fois qu'un visiteur accède à http://example.com/de-DE/Home/Index, le site allemand s'affiche.

J'espère que cette réponse vous oriente dans la bonne direction.

J'ai également fait un petit exemple de projet MVC 5 que vous pouvez trouver ici

Accédez simplement à http: // {yourhost}: {port} / en-us / home / index pour voir la date actuelle en anglais (États-Unis), ou modifiez-la en http: // {yourhost}: {port} / de -de / home / index pour l'allemand, etc.

jao
la source
15
J'aime aussi mettre le lang dans l'URL, car il est devenu crawlable par les moteurs de recherche dans différentes langues et permet à l'utilisateur d'enregistrer ou d'envoyer une URL avec un langage spécifique.
Eduardo Molteni
50
L'ajout de la langue à l'URL ne viole pas REST. En fait, il y adhère en rendant la ressource Web non dépendante d'un état de session masqué.
Jace Rhea
4
La ressource Web ne dépend pas d'un état masqué, la façon dont elle est rendue l'est. Si vous souhaitez accéder à la ressource en tant que service Web, vous devrez choisir une langue pour le faire.
Dave Van den Eynde
4
J'ai eu quelques problèmes avec ce type de solution. Les messages d'erreur de validation n'étaient pas en cours de traduction. Pour résoudre le problème, j'ai défini la culture dans la fonction Application_AcquireRequestState du fichier global.asax.cs.
ADH
4
Mettre ceci dans un filtre n'est PAS une bonne idée. La liaison de modèle utilise la CurrentCulture, mais l'ActionFilter se produit après la liaison de modèle. Il est préférable de le faire dans Global.asax, Application_PreRequestHandlerExecute.
Stefan
38

Je sais que c'est une vieille question, mais si vous souhaitez vraiment que cela fonctionne avec votre ModelBinder (en ce qui concerne DefaultModelBinder.ResourceClassKey = "MyResource";ainsi que les ressources indiquées dans les annotations de données des classes viewmodel), le contrôleur ou même un ActionFilterest trop tard pour définir la culture .

La culture peut être définie Application_AcquireRequestState, par exemple:

protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        // For example a cookie, but better extract it from the url
        string culture = HttpContext.Current.Request.Cookies["culture"].Value;

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
    }

ÉDITER

En fait, il existe un meilleur moyen d'utiliser un gestionnaire d'itinéraire personnalisé qui définit la culture en fonction de l'url, parfaitement décrite par Alex Adamyan sur son blog .

Tout ce qu'il y a à faire est de remplacer la GetHttpHandlerméthode et d'y définir la culture.

public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // get culture from route data
        var culture = requestContext.RouteData.Values["culture"].ToString();
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
        return base.GetHttpHandler(requestContext);
    }
}
marapet
la source
Malheureusement, RouteData etc. ne sont pas disponibles dans la méthode "Application_AcquireRequestState" mais ils sont dans Controller.CreateActionInvoker (). Je suggère donc de "remplacer IActionInvoker CreateActionInvoker () protégé" et de définir CultureInfo ici.
Skorunka František
J'ai lu ce blog. Y a-t-il un problème si je continue avec le cookie? Puisque je n'ai pas la permission de le changer. Veuillez m'en informer. y a-t-il un problème avec cette approche?
kbvishnu
@VeeKeyBee Si votre site est public, toutes les langues ne seront pas indexées correctement lors de l'utilisation de cookies, pour les sites protégés, vous allez probablement bien.
marapet
pas ce n'est pas public. Pouvez-vous s'il vous plaît donner un indice sur le mot «indexé»?
kbvishnu
1
Vous devriez poser votre propre question et lire sur le référencement, cela n'a plus rien à voir avec la question d'origine. webmasters.stackexchange.com/questions/3786/…
marapet
25

Je le ferais dans l'événement Initialize du contrôleur comme ceci ...

    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
        base.Initialize(requestContext);

        const string culture = "en-US";
        CultureInfo ci = CultureInfo.GetCultureInfo(culture);

        Thread.CurrentThread.CurrentCulture = ci;
        Thread.CurrentThread.CurrentUICulture = ci;
    }
Jace Rhea
la source
1
la chaîne de culture ne peut pas être un const, car l'utilisateur doit pouvoir spécifier la culture qu'il souhaite utiliser sur le site.
NerdFury
2
Je comprends cela, mais la question était de savoir où il était préférable de définir la culture et non comment la définir.
Jace Rhea
Au lieu d'un const, vous pouvez utiliser quelque chose comme: var newCulture = new CultureInfo (RouteData.Values ​​["lang"]. ToString ());
Nordes
AuthorizeCore est appelé avant OnActionExecuting, vous n'aurez donc aucun détail de culture dans votre méthode remplacée AuthorizeCore. L'utilisation de la méthode d'initialisation du contrôleur peut fonctionner mieux, en particulier si vous implémentez un AuthorizeAttribute personnalisé, car la méthode Initialize est appelée avant AuthorizeCore (vous aurez des détails de culture dans AuthorizeCore).
Nathan R
7

Étant donné qu'il s'agit d'un paramètre stocké par utilisateur, la session est un endroit approprié pour stocker les informations.

Je changerais votre contrôleur pour prendre la chaîne de culture comme paramètre, plutôt que d'avoir une méthode d'action différente pour chaque culture potentielle. L'ajout d'un lien vers la page est facile et vous ne devriez pas avoir besoin d'écrire le même code à plusieurs reprises chaque fois qu'une nouvelle culture est requise.

public class CultureController : Controller    
{
        public ActionResult SetCulture(string culture)
        {
            HttpContext.Session["culture"] = culture
            return RedirectToAction("Index", "Home");
        }        
}

<li><%= Html.ActionLink("French", "SetCulture", new {controller = "Culture", culture = "fr-FR"})%></li>
<li><%= Html.ActionLink("Spanish", "SetCulture", new {controller = "Culture", culture = "es-ES"})%></li>
NerdFury
la source
merci pour la réponse, je n'essaye pas de deciede s'il faut utiliser la session ou non. Je suis content de ça. Ce que j'essaie de déterminer, c'est s'il est préférable de le faire dans un contrôleur de culture qui a une méthode d'action pour chaque culture à définir Ou y a-t-il un meilleur endroit dans le pipeline MVC pour le faire
ChrisCa
J'ai fourni une réponse modifiée qui correspond mieux à la question.
NerdFury
Oui, c'est certainement plus propre, mais ce que je veux vraiment savoir, c'est si cela doit être fait dans un contrôleur. Ou s'il existe un meilleur endroit dans le pipeline MVC pour définir Culture. Ou si c'est mieux dans un ActionFilters, Handlers, Modules etc
ChrisCa
Un gestionnaire et un module n'ont pas de sens car l'utilisateur n'a pas eu la possibilité de faire une sélection. Vous avez besoin d'un moyen pour l'utilisateur de faire une sélection, puis de traiter la sélection des utilisateurs, qui sera effectuée dans un contrôleur.
NerdFury
convenu, les gestionnaires et les modules sont trop tôt pour permettre l'interaction de l'utilisateur. Cependant, je suis assez nouveau dans MVC, je ne suis donc pas sûr que ce soit le meilleur endroit dans le pipeline pour le définir. Si je n'entends pas le contraire après un certain temps, j'accepterai votre réponse. ps que la syntaxe que vous avez utilisée pour passer un paramètre à une méthode Action ne semble pas fonctionner. Il n'a pas de contrôleur défini donc utilise simplement celui par défaut (qui n'est pas le bon dans ce cas). Et il ne semble pas y avoir une autre surcharge appropriée
ChrisCa
6

Quel est le meilleur endroit est votre question. Le meilleur endroit est à l'intérieur du contrôleur. méthode . MSDN écrit qu'il est appelé après le constructeur et avant la méthode d'action. Contrairement à la substitution de OnActionExecuting, placer votre code dans la méthode Initialize vous permet de bénéficier de toutes les annotations et attributs de données personnalisés sur vos classes et sur vos propriétés à localiser.

Par exemple, ma logique de localisation provient d'une classe qui est injectée dans mon contrôleur personnalisé. J'ai accès à cet objet puisque Initialize est appelé après le constructeur. Je peux faire l'attribution de culture du fil et ne pas afficher tous les messages d'erreur correctement.

 public BaseController(IRunningContext runningContext){/*...*/}

 protected override void Initialize(RequestContext requestContext)
 {
     base.Initialize(requestContext);
     var culture = runningContext.GetCulture();
     Thread.CurrentThread.CurrentUICulture = culture;
     Thread.CurrentThread.CurrentCulture = culture;
 }

Même si votre logique n'est pas à l'intérieur d'une classe comme l'exemple que j'ai fourni, vous avez accès au RequestContext qui vous permet d'avoir l'URL et HttpContext et le RouteData que vous pouvez faire pratiquement n'importe quelle analyse possible.

Patrick Desjardins
la source
Cela fonctionne pour mon HTML5 Telerik ReportLocalization !. Merci @Patrick Desjardins
CoderRoller
4

Si vous utilisez des sous-domaines, par exemple comme "pt.mydomain.com" pour définir le portugais par exemple, utiliser Application_AcquireRequestState ne fonctionnera pas, car il n'est pas appelé lors des requêtes de cache suivantes.

Pour résoudre cela, je suggère une implémentation comme celle-ci:

  1. Ajoutez le paramètre VaryByCustom à OutPutCache comme ceci:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. Dans global.asax.cs, récupérez la culture de l'hôte à l'aide d'un appel de fonction:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. Ajoutez la fonction GetCultureFromHost à global.asax.cs:

    private CultureInfo GetCultureFromHost()
    {
        CultureInfo ci = new CultureInfo("en-US"); // en-US
        string host = Request.Url.Host.ToLower();
        if (host.Equals("mydomain.com"))
        {
            ci = new CultureInfo("en-US");
        }
        else if (host.StartsWith("pt."))
        {
            ci = new CultureInfo("pt");
        }
        else if (host.StartsWith("de."))
        {
            ci = new CultureInfo("de");
        }
        else if (host.StartsWith("da."))
        {
            ci = new CultureInfo("da");
        }
    
        return ci;
    }
    
  4. Et enfin, remplacez GetVaryByCustomString (...) pour utiliser également cette fonction:

    public override string GetVaryByCustomString(HttpContext context, string value)
    {
        if (value.ToLower() == "lang")
        {
            CultureInfo ci = GetCultureFromHost();
            return ci.Name;
        }
        return base.GetVaryByCustomString(context, value);
    }
    

La fonction Application_AcquireRequestState est appelée sur les appels non mis en cache, ce qui permet au contenu d'être généré et mis en cache. GetVaryByCustomString est appelé sur les appels mis en cache pour vérifier si le contenu est disponible dans le cache, et dans ce cas, nous examinons la valeur du domaine hôte entrant, encore une fois, au lieu de nous fier uniquement aux informations de culture actuelles, qui auraient pu changer pour la nouvelle demande (car nous utilisons des sous-domaines).

Andy
la source
4

1: Créez un attribut personnalisé et remplacez la méthode comme ceci:

public class CultureAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
    // Retreive culture from GET
    string currentCulture = filterContext.HttpContext.Request.QueryString["culture"];

    // Also, you can retreive culture from Cookie like this :
    //string currentCulture = filterContext.HttpContext.Request.Cookies["cookie"].Value;

    // Set culture
    Thread.CurrentThread.CurrentCulture = new CultureInfo(currentCulture);
    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(currentCulture);
    }
}

2: Dans App_Start, recherchez FilterConfig.cs, ajoutez cet attribut. (cela fonctionne pour toute l'application)

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
    // Add custom attribute here
    filters.Add(new CultureAttribute());
    }
}    

C'est ça !

Si vous souhaitez définir la culture pour chaque contrôleur / action au lieu de l'application entière, vous pouvez utiliser cet attribut comme ceci:

[Culture]
public class StudentsController : Controller
{
}

Ou:

[Culture]
public ActionResult Index()
{
    return View();
}
Meng Xue
la source
0
protected void Application_AcquireRequestState(object sender, EventArgs e)
        {
            if(Context.Session!= null)
            Thread.CurrentThread.CurrentCulture =
                    Thread.CurrentThread.CurrentUICulture = (Context.Session["culture"] ?? (Context.Session["culture"] = new CultureInfo("pt-BR"))) as CultureInfo;
        }
user2861593
la source
3
Veuillez expliquer pourquoi c'est censé être le meilleur moyen.
Max Leske