Festlegen der Kultur in einer ASP.Net MVC-App

85

Was ist der beste Ort, um die Kultur / UI-Kultur in einer ASP.net MVC-App festzulegen?

Derzeit habe ich eine CultureController-Klasse, die folgendermaßen aussieht:

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

und einen Hyperlink für jede Sprache auf der Homepage mit einem Link wie diesem:

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

Das funktioniert gut, aber ich denke, es gibt einen angemesseneren Weg, dies zu tun.

Ich lese die Kultur mit dem folgenden ActionFilter: http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx . Ich bin ein bisschen ein MVC-Noob, daher bin ich nicht sicher, ob ich dies an der richtigen Stelle einstelle. Ich möchte es nicht auf der Ebene web.config tun, es muss auf der Wahl eines Benutzers basieren. Ich möchte auch nicht ihre http-Header überprüfen, um die Kultur aus ihren Browsereinstellungen zu erhalten.

Bearbeiten:

Nur um klar zu sein - ich versuche nicht zu entscheiden, ob ich eine Sitzung verwenden möchte oder nicht. Ich bin glücklich mit diesem bisschen. Ich versuche herauszufinden, ob es am besten ist, dies in einem Kultur-Controller zu tun, für den für jede Kultur eine Aktionsmethode festgelegt werden soll, oder ob es in der MVC-Pipeline einen besseren Ort gibt, um dies zu tun.

ChrisCa
quelle
Die Verwendung des Sitzungsstatus zur Auswahl der Benutzerkultur ist keine gute Wahl. Der beste Weg ist, die Kultur als Teil der URL aufzunehmen , wodurch es einfach ist, die aktuelle Seite mit einer anderen Kultur zu "tauschen".
NightOwl888

Antworten:

111

Ich verwende diese Lokalisierungsmethode und füge einen Routenparameter hinzu, der die Kultur und Sprache festlegt, wenn ein Benutzer example.com/xx-xx/ besucht.

Beispiel:

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

Ich habe einen Filter, der die eigentliche Kultur- / Spracheinstellung vornimmt:

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

    }
}

Um das Internationalisierungsattribut zu aktivieren, fügen Sie es einfach Ihrer Klasse hinzu:

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

Immer wenn ein Besucher zu http://example.com/de-DE/Home/Index geht, wird die deutsche Website angezeigt.

Ich hoffe, diese Antwort weist Sie in die richtige Richtung.

Ich habe auch ein kleines MVC 5-Beispielprojekt erstellt, das Sie hier finden

Gehen Sie einfach zu http: // {yourhost}: {port} / de-de / home / index, um das aktuelle Datum in Englisch (USA) anzuzeigen, oder ändern Sie es in http: // {yourhost}: {port} / de -de / home / index für deutsch etcetera.

jao
quelle
15
Ich mag es auch, die Sprache in die URL einzufügen, da sie von Suchmaschinen in verschiedenen Sprachen gecrawlt werden kann und es dem Benutzer ermöglicht, eine URL mit einer bestimmten Sprache zu speichern oder zu senden.
Eduardo Molteni
50
Das Hinzufügen der Sprache zur URL verstößt nicht gegen REST. Es hält sich daran, indem es die Webressource nicht von einem verborgenen Sitzungsstatus abhängig macht.
Jace Rhea
4
Die Webressource ist nicht von einem versteckten Status abhängig, sondern von der Art und Weise, wie sie gerendert wird. Wenn Sie als Webdienst auf die Ressource zugreifen möchten, müssen Sie eine Sprache auswählen, in der dies ausgeführt werden soll.
Dave Van den Eynde
4
Ich hatte einige Probleme mit dieser Art von Lösung. Die Validierungsfehlermeldungen wurden nicht übersetzt. Um das Problem zu lösen, habe ich die Kultur in der Application_AcquireRequestState-Funktion der Datei global.asax.cs festgelegt.
ADH
4
Dies in einen Filter zu setzen ist keine gute Idee. Die Modellbindung verwendet die CurrentCulture, der ActionFilter erfolgt jedoch nach der Modellbindung. Dies ist besser in Global.asax, Application_PreRequestHandlerExecute.
Stefan
38

Ich weiß, dass dies eine alte Frage ist, aber wenn Sie wirklich möchten, dass dies mit Ihrem ModelBinder funktioniert (sowohl in Bezug auf DefaultModelBinder.ResourceClassKey = "MyResource";die in den Datenanmerkungen der viewmodel-Klassen angegebenen Ressourcen), ist der Controller oder sogar ein ActionFilterzu spät Stellen Sie die Kultur ein .

Die Kultur könnte Application_AcquireRequestStatezum Beispiel gesetzt werden:

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

BEARBEITEN

Tatsächlich gibt es einen besseren Weg, einen benutzerdefinierten Routehandler zu verwenden, der die Kultur gemäß der URL festlegt, die Alex Adamyan in seinem Blog perfekt beschrieben hat .

Alles was zu tun ist, ist die GetHttpHandlerMethode zu überschreiben und die Kultur dort festzulegen.

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
quelle
Leider sind RouteData usw. in der Methode "Application_AcquireRequestState" nicht verfügbar, aber in Controller.CreateActionInvoker (). Also schlage ich vor, "geschützte Überschreibung von IActionInvoker CreateActionInvoker ()" und CultureInfo genau dort zu setzen.
Skorunka František
Ich habe diesen Blog gelesen. Gibt es ein Problem, wenn ich mit Cookies fortfahre? Da ich keine Erlaubnis habe, es zu ändern. Bitte informieren Sie mich. Gibt es ein Problem mit diesem Ansatz?
Kbvishnu
@VeeKeyBee Wenn Ihre Website öffentlich ist, werden bei Verwendung von Cookies nicht alle Sprachen ordnungsgemäß indiziert. Für geschützte Websites ist dies wahrscheinlich in Ordnung.
Marapet
nicht es ist nicht öffentlich. Können Sie bitte einen Hinweis auf das Wort "indiziert" geben?
kbvishnu
1
Sie sollten Ihre eigene Frage stellen und sich über SEO informieren, dies hat nichts mehr mit der ursprünglichen Frage zu tun. webmasters.stackexchange.com/questions/3786/...
marapet
25

Ich würde es im Initialize-Ereignis des Controllers so machen ...

    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
quelle
1
Die Kulturzeichenfolge kann keine Konstante sein, da der Benutzer in der Lage sein muss, die Kultur anzugeben, die er auf der Site verwenden möchte.
NerdFury
2
Ich verstehe das, aber die Frage war, wo es am besten ist, die Kultur zu setzen, nicht wie man sie setzt.
Jace Rhea
Anstelle einer Konstante können Sie Folgendes verwenden: var newCulture = new CultureInfo (RouteData.Values ​​["lang"]. ToString ());
Nordes
AuthorizeCore wird vor OnActionExecuting aufgerufen, sodass Ihre AuthorizeCore-überschriebene Methode keine Kulturdetails enthält. Die Verwendung der Initialisierungsmethode des Controllers funktioniert möglicherweise besser, insbesondere wenn Sie ein benutzerdefiniertes AuthorizeAttribute implementieren, da die Initialize-Methode vor AuthorizeCore aufgerufen wird (in AuthorizeCore sind Kulturdetails enthalten).
Nathan R
7

Da es sich um eine Einstellung handelt, die pro Benutzer gespeichert wird, ist die Sitzung ein geeigneter Ort zum Speichern der Informationen.

Ich würde Ihren Controller so ändern, dass die Kulturzeichenfolge als Parameter verwendet wird, anstatt für jede potenzielle Kultur eine andere Aktionsmethode zu verwenden. Das Hinzufügen eines Links zur Seite ist einfach und Sie sollten nicht immer denselben Code schreiben müssen, wenn eine neue Kultur erforderlich ist.

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
quelle
Vielen Dank für die Antwort. Ich versuche nicht zu entscheiden, ob ich die Sitzung verwenden soll oder nicht. Ich bin glücklich mit diesem bisschen. Ich versuche herauszufinden, ob es am besten ist, dies in einem Kultur-Controller zu tun, der eine Aktionsmethode für jede einzustellende Kultur hat. Oder gibt es einen besseren Ort in der MVC-Pipeline, um dies zu tun
ChrisCa,
Ich habe eine bearbeitete Antwort bereitgestellt, die besser zur Frage passt.
NerdFury
Ja, das ist sicherlich sauberer, aber ich möchte wirklich wissen, ob dies überhaupt in einem Controller durchgeführt werden sollte. Oder wenn es einen besseren Ort in der MVC-Pipeline gibt, um Culture festzulegen. Oder wenn es in einem ActionFilters, Handler, Modul usw. besser ist
ChrisCa
Ein Handler und ein Modul sind nicht sinnvoll, da der Benutzer keine Möglichkeit hatte, eine Auswahl zu treffen. Sie benötigen eine Möglichkeit für den Benutzer, eine Auswahl zu treffen und dann die Benutzerauswahl zu verarbeiten, die in einem Controller ausgeführt wird.
NerdFury
vereinbart, Handler und Module sind zu früh, um Benutzerinteraktion zu ermöglichen. Ich bin jedoch ziemlich neu in MVC, daher bin ich mir nicht sicher, ob dies der beste Ort in der Pipeline ist, um es einzustellen. Wenn ich nach einer Weile nichts anderes höre, akzeptiere ich Ihre Antwort. ps Die Syntax, mit der Sie einen Parameter an eine Action-Methode übergeben haben, scheint nicht zu funktionieren. Da kein Controller definiert ist, wird nur der Standard-Controller verwendet (der in diesem Fall nicht der richtige ist). Und es scheint keine andere geeignete Überlastung zu geben
ChrisCa
6

Was ist der beste Ort ist Ihre Frage. Der beste Platz befindet sich in der Controller.Initialize- Methode. MSDN schreibt, dass es nach dem Konstruktor und vor der Aktionsmethode aufgerufen wird. Im Gegensatz zum Überschreiben von OnActionExecuting können Sie durch Platzieren Ihres Codes in der Initialize-Methode alle benutzerdefinierten Datenanmerkungen und -attribute für Ihre Klassen und Ihre zu lokalisierenden Eigenschaften nutzen.

Meine Lokalisierungslogik stammt beispielsweise aus einer Klasse, die in meinen benutzerdefinierten Controller eingefügt wird. Ich habe Zugriff auf dieses Objekt, da Initialize nach dem Konstruktor aufgerufen wird. Ich kann die Kulturzuweisung des Threads durchführen und nicht jede Fehlermeldung korrekt anzeigen lassen.

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

Auch wenn sich Ihre Logik nicht in einer Klasse wie dem von mir bereitgestellten befindet, haben Sie Zugriff auf den RequestContext, mit dem Sie die URL und den HttpContext sowie die RouteData verwenden können, mit denen Sie grundsätzlich jede mögliche Analyse durchführen können.

Patrick Desjardins
quelle
Dies funktioniert für meine HTML5 Telerik ReportLocalization!. Danke @Patrick Desjardins
CoderRoller
4

Wenn Sie Subdomains verwenden, z. B. "pt.mydomain.com", um beispielsweise Portugiesisch festzulegen, funktioniert die Verwendung von Application_AcquireRequestState nicht, da sie bei nachfolgenden Cache-Anforderungen nicht aufgerufen wird.

Um dies zu lösen, schlage ich eine Implementierung wie diese vor:

  1. Fügen Sie den VaryByCustom-Parameter wie folgt zum OutPutCache hinzu:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
  2. Rufen Sie in global.asax.cs die Kultur mithilfe eines Funktionsaufrufs vom Host ab:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
  3. Fügen Sie die Funktion GetCultureFromHost zu global.asax.cs hinzu:

    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. Und schließlich überschreiben Sie GetVaryByCustomString (...), um auch diese Funktion zu verwenden:

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

Die Funktion Application_AcquireRequestState wird bei nicht zwischengespeicherten Aufrufen aufgerufen, wodurch der Inhalt generiert und zwischengespeichert werden kann. GetVaryByCustomString wird bei zwischengespeicherten Aufrufen aufgerufen, um zu überprüfen, ob der Inhalt im Cache verfügbar ist. In diesem Fall untersuchen wir erneut den Wert der eingehenden Hostdomäne, anstatt uns nur auf die aktuellen Kulturinformationen zu verlassen, die sich für die neue Anforderung möglicherweise geändert haben (weil wir verwenden Subdomains).

Andy
quelle
4

1: Erstellen Sie ein benutzerdefiniertes Attribut und überschreiben Sie die Methode wie folgt:

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: Suchen Sie in App_Start nach FilterConfig.cs und fügen Sie dieses Attribut hinzu. (Dies funktioniert für die gesamte Anwendung)

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

Das ist es !

Wenn Sie anstelle der gesamten Anwendung eine Kultur für jeden Controller / jede Aktion definieren möchten, können Sie dieses Attribut wie folgt verwenden:

[Culture]
public class StudentsController : Controller
{
}

Oder:

[Culture]
public ActionResult Index()
{
    return View();
}
Meng Xue
quelle
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
quelle
3
Bitte erklären Sie, warum dies der beste Weg sein soll.
Max Leske