web-dev-qa-db-fra.com

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

Quel est le meilleur endroit pour définir Culture/UI Culture 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 de 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>

ce qui fonctionne bien mais je pense qu’il existe un moyen plus approprié de le faire.

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 noC MVC, je ne suis donc pas convaincu de le placer au bon endroit. Je ne veux pas le faire au niveau de web.config, cela doit être basé sur le choix de l'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.

Modifier:

Soyons clairs: je n’essaie pas de décider d’utiliser session ou non. Je suis heureux avec ce bit. Ce que j'essaie de comprendre, c'est s'il est préférable de le faire dans un contrôleur de culture disposant d'une méthode d'action pour chaque culture à définir, ou s'il existe un meilleur emplacement dans le pipeline MVC pour le faire.

81
ChrisCa

J'utilise ceci méthode de localisation et j'ai ajouté un paramètre de route 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 définit le paramètre culture/langue actuel:

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 Internationalization, ajoutez-le simplement à votre classe:

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

Maintenant, chaque fois qu'un visiteur va à http://example.com/de-DE/Home/Index , le site allemand est affiché.

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

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

Allez simplement à http: // {yourhost}: {port}/en-us/home/index pour voir la date actuelle en anglais (US), ou changez-la en http: // {yourhost}: {port}/de -de/home/index pour l'allemand, etc.

109
jao

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 ActionFilter est trop tard pour définir la culture .

La culture pourrait être définie dans 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 réalité, il existe un meilleur moyen d'utiliser un gestionnaire de route personnalisé qui définit la culture en fonction de l'URL, parfaitement décrit par Alex Adamyan sur son blog .

Tout ce que vous avez à faire est de remplacer la méthode GetHttpHandler 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);
    }
}
37
marapet

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

    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;
    }
24
Jace Rhea

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

Je voudrais changer votre contrôleur pour prendre la chaîne de culture en tant que paramètre, plutôt que d'avoir une méthode d'action différente pour chaque culture potentielle. L'ajout d'un lien à la page est facile et vous ne devriez pas avoir besoin d'écrire le même code de manière répétée 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>
7
NerdFury

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

Par exemple, ma logique de localisation provient d'une classe 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'affectation de culture du Thread et ne pas avoir tous les messages d'erreur affichés 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 dans une classe comme dans l’exemple que j’ai fourni, vous avez accès à RequestContext qui vous permet de: avoir l'URL et HttpContext et le RouteData que vous pouvez faire fondamentalement analyse syntaxique possible.

5

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 de demandes de cache ultérieures.

Pour résoudre ceci, 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, obtenez 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ée sur les appels mis en cache pour vérifier si le contenu est disponible en cache. Dans ce cas, nous examinons à nouveau la valeur du domaine hôte entrant, au lieu de nous fier uniquement aux informations de culture actuelles, qui auraient pu être modifiées pour la nouvelle demande (car nous utilisons des sous-domaines).

4
Andy

1: Créez un attribut personnalisé et substituez 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 d'une application entière, vous pouvez utiliser cet attribut comme suit:

[Culture]
public class StudentsController : Controller
{
}

Ou:

[Culture]
public ActionResult Index()
{
    return View();
}
4
Meng Xue
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;
        }
0
user2861593