Imposta la cultura in un'app ASP.Net MVC


85

Qual è il posto migliore per impostare la cultura / cultura dell'interfaccia utente in un'app ASP.net MVC

Attualmente ho una classe CultureController simile a questa:

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");
    }
}

e un collegamento ipertestuale per ogni lingua sulla home page con un collegamento come questo:

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

che funziona bene, ma penso che ci sia un modo più appropriato per farlo.

Sto leggendo la cultura utilizzando il seguente ActionFilter http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx . Sono un po 'un noob MVC quindi non sono sicuro di averlo impostato nel posto giusto. Non voglio farlo a livello di web.config, deve essere basato sulla scelta di un utente. Inoltre, non voglio controllare i loro header http per ottenere la cultura dalle impostazioni del browser.

Modificare:

Giusto per essere chiari, non sto cercando di decidere se utilizzare o meno la sessione. Sono contento di quel bit. Quello che sto cercando di capire è se è meglio farlo in un controller di cultura che ha un metodo di azione per ogni cultura da impostare, oppure c'è un posto migliore nella pipeline MVC per farlo?


Utilizzare lo stato della sessione per selezionare la cultura dell'utente non è una buona scelta. Il modo migliore è includere la cultura come parte dell'URL , il che rende facile "scambiare" la pagina corrente con un'altra cultura.
NightOwl888

Risposte:


114

Sto usando questo metodo di localizzazione e ho aggiunto un parametro di percorso che imposta la cultura e la lingua ogni volta che un utente visita example.com/xx-xx/

Esempio:

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

Ho un filtro che esegue l'impostazione della lingua / cultura effettiva:

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));

    }
}

Per attivare l'attributo Internationalization, aggiungilo semplicemente alla tua classe:

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

Ora ogni volta che un visitatore va su http://example.com/de-DE/Home/Index , viene visualizzato il sito tedesco.

Spero che questa risposta ti indichi nella giusta direzione.

Ho anche realizzato un piccolo progetto di esempio MVC 5 che puoi trovare qui

Vai a http: // {yourhost}: {port} / en-us / home / index per vedere la data corrente in inglese (USA) o modificala in http: // {yourhost}: {port} / de -de / home / index per il tedesco eccetera.


15
Mi piace anche inserire la lingua nell'URL, perché è diventata sottoponibile a scansione dai motori di ricerca in diverse lingue e consente all'utente di salvare o inviare un URL con una lingua specifica.
Eduardo Molteni

50
L'aggiunta della lingua all'URL non viola REST. Infatti vi aderisce rendendo la risorsa web non dipendente da uno stato di sessione nascosto.
Jace Rhea

4
La risorsa web non dipende da uno stato nascosto, come lo è il rendering. Se vuoi accedere alla risorsa come servizio web, dovrai scegliere una lingua per farlo.
Dave Van den Eynde

4
Ho avuto alcuni problemi con questo tipo di soluzione. I messaggi di errore di convalida non venivano tradotti. Per risolvere il problema ho impostato la cultura nella funzione Application_AcquireRequestState del file global.asax.cs.
ADH

4
Metterlo in un filtro NON è una buona idea. L'associazione di modelli utilizza CurrentCulture, ma ActionFilter si verifica dopo l'associazione di modelli. È meglio farlo in Global.asax, Application_PreRequestHandlerExecute.
Stefan

38

So che questa è una vecchia domanda, ma se vuoi davvero che funzioni con il tuo ModelBinder (rispetto alle DefaultModelBinder.ResourceClassKey = "MyResource";risorse indicate nelle annotazioni dei dati delle classi viewmodel), il controller o anche unActionFilter è troppo tardi per impostare la cultura .

La cultura potrebbe essere impostata Application_AcquireRequestState, ad esempio:

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);
    }

MODIFICARE

In realtà c'è un modo migliore di utilizzare un routehandler personalizzato che imposta la cultura in base all'URL , perfettamente descritto da Alex Adamyan sul suo blog .

Tutto quello che c'è da fare è sovrascrivere il GetHttpHandlermetodo e impostare la cultura lì.

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);
    }
}

Sfortunatamente RouteData ecc. Non sono disponibili nel metodo "Application_AcquireRequestState" ma sono in Controller.CreateActionInvoker (). Quindi suggerisco di "sovrascrivere protetto IActionInvoker CreateActionInvoker ()" e impostare CultureInfo proprio lì.
Skorunka František

Ho letto quel blog. C'è qualche problema se vado avanti con i cookie? Dal momento che non ho il permesso di cambiarlo. Si prega gentilmente di informarmi. c'è qualche problema con questo approccio?
kbvishnu

@VeeKeyBee Se il tuo sito è pubblico, tutte le lingue non verranno indicizzate correttamente quando usi i cookie, per i siti protetti probabilmente stai bene.
marapet

non è pubblico. Puoi dare un suggerimento sulla parola "indicizzato"?
kbvishnu

1
Dovresti fare la tua domanda e leggere su SEO, questo non ha più nulla a che fare con la domanda originale. webmasters.stackexchange.com/questions/3786/…
marapet

25

Lo farei nell'evento Initialize del controller in questo modo ...

    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;
    }

1
la stringa della cultura non può essere una const, poiché l'utente deve essere in grado di specificare la cultura che vorrebbe utilizzare sul sito.
NerdFury

2
Lo capisco, ma la domanda era dove fosse meglio impostare la cultura non come impostarla.
Jace Rhea

Invece di una const, puoi usare qualcosa come: var newCulture = new CultureInfo (RouteData.Values ​​["lang"]. ToString ());
Nordes

AuthorizeCore viene chiamato prima di OnActionExecuting, quindi non avrai alcun dettaglio di cultura nel tuo metodo sottoposto a override AuthorizeCore. L'uso del metodo di inizializzazione del controller potrebbe funzionare meglio, soprattutto se si implementa un AuthorizeAttribute personalizzato, poiché il metodo Initialize viene chiamato prima di AuthorizeCore (si avranno i dettagli della cultura all'interno di AuthorizeCore).
Nathan R

7

Essendo un'impostazione memorizzata per utente, la sessione è un luogo appropriato per memorizzare le informazioni.

Vorrei cambiare il tuo controller per prendere la stringa della cultura come parametro, piuttosto che avere un metodo di azione diverso per ogni potenziale cultura. Aggiungere un collegamento alla pagina è facile e non dovrebbe essere necessario scrivere lo stesso codice ripetutamente ogni volta che è richiesta una nuova cultura.

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>

grazie per la risposta, non sto cercando di decidere se utilizzare o meno la sessione. Sono contento di quel po '. Quello che sto cercando di capire è se è meglio farlo in un controller Culture che ha un metodo di azione per ogni Culture da impostare O c'è un posto migliore nella pipeline MVC per farlo
ChrisCa

Ho fornito una risposta modificata che si adatta meglio alla domanda.
NerdFury

Sì, è certamente più pulito, ma quello che voglio davvero sapere è se questo dovrebbe essere fatto in un controller. O se c'è un posto migliore nella pipeline MVC per impostare Culture. O se è meglio in un ActionFilters, Handlers, Modules, ecc
ChrisCa

Un gestore e un modulo non hanno senso perché l'utente non ha avuto la possibilità di effettuare una selezione. È necessario un modo per consentire all'utente di effettuare una selezione e quindi elaborare la selezione degli utenti, che verrà eseguita in un controller.
NerdFury

d'accordo, gestori e moduli sono troppo precoci per consentire l'interazione dell'utente. Tuttavia, sono abbastanza nuovo per MVC, quindi non sono sicuro che questo sia il posto migliore nella pipeline per impostarlo. Se dopo un po 'non sento il contrario, accetterò la tua risposta. ps la sintassi che hai usato per passare un parametro a un metodo Action non sembra funzionare. Non ha un controller definito, quindi utilizza solo quello predefinito (che non è quello corretto in questo caso). E non sembra esserci un altro sovraccarico adatto
ChrisCa

6

Qual è il posto migliore è la tua domanda. Il posto migliore è all'interno del Controller.Initialize metodo . MSDN scrive che viene chiamato dopo il costruttore e prima del metodo di azione. Al contrario di sovrascrivere OnActionExecuting, l'inserimento del codice nel metodo Initialize consente di trarre vantaggio dall'avere tutte le annotazioni e gli attributi dei dati personalizzati sulle classi e sulle proprietà da localizzare.

Ad esempio, la mia logica di localizzazione proviene da una classe che viene iniettata nel mio controller personalizzato. Ho accesso a questo oggetto poiché Initialize viene chiamato dopo il costruttore. Posso eseguire l'assegnazione della cultura del thread e non visualizzare correttamente tutti i messaggi di errore.

 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;
 }

Anche se la tua logica non è all'interno di una classe come l'esempio che ho fornito, hai accesso a RequestContext che ti consente di avere l'URL e HttpContext e RouteData che puoi eseguire praticamente qualsiasi analisi possibile.


Funziona con il mio HTML5 Telerik ReportLocalization !. Grazie @Patrick Desjardins
CoderRoller

4

Se si utilizzano i sottodomini, ad esempio come "pt.mydomain.com" per impostare il portoghese, ad esempio, l'utilizzo di Application_AcquireRequestState non funzionerà, perché non viene chiamato nelle successive richieste di cache.

Per risolvere questo problema, suggerisco un'implementazione come questa:

  1. Aggiungi il parametro VaryByCustom a OutPutCache in questo modo:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. In global.asax.cs, ottieni la cultura dall'host utilizzando una chiamata di funzione:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. Aggiungi la funzione GetCultureFromHost a 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. E infine sovrascrivi il GetVaryByCustomString (...) per utilizzare anche questa funzione:

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

La funzione Application_AcquireRequestState viene chiamata su chiamate non memorizzate nella cache, che consente al contenuto di essere generato e memorizzato nella cache. GetVaryByCustomString viene chiamato sulle chiamate memorizzate nella cache per verificare se il contenuto è disponibile nella cache, e in questo caso esaminiamo di nuovo il valore del dominio host in entrata, invece di fare affidamento solo sulle informazioni della cultura corrente, che potrebbero essere cambiate per la nuova richiesta (perché stiamo usando sottodomini).


4

1: crea un attributo personalizzato e sostituisci un metodo come questo:

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: in App_Start, trova FilterConfig.cs, aggiungi questo attributo. (funziona per TUTTA l'applicazione)

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

Questo è tutto !

Se vuoi definire la cultura per ogni controller / azione al posto dell'intera applicazione, puoi usare questo attributo in questo modo:

[Culture]
public class StudentsController : Controller
{
}

O:

[Culture]
public ActionResult Index()
{
    return View();
}

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;
        }

3
Per favore, spiega perché questo dovrebbe essere il modo migliore.
Max Leske
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.