Set Culture in an ASP.Net MVC app

2019-01-01 06:14发布

What is the best place to set the Culture/UI Culture in an ASP.net MVC app

Currently I have a CultureController class which looks like this:

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

and a hyperlink for each language on the homepage with a link such as this:

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

which works fine but I am thinking there is a more appropriate way to do this.

I am reading the Culture using the following ActionFilter http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx. I am a bit of an MVC noob so am not confident I am setting this in the correct place. I don't want to do it at the web.config level, it has to be based on a user's choice. I also don't want to check their http-headers to get the culture from their browser settings.

Edit:

Just to be clear - I am not trying to decide whether to use session or not. I am happy with that bit. What I am trying to work out is if it is best to do this in a Culture controller that has an action method for each Culture to be set, or is there is a better place in the MVC pipeline to do this?

8条回答
与风俱净
2楼-- · 2019-01-01 06:53

I know this is an old question, but if you really would like to have this working with your ModelBinder (in respect to DefaultModelBinder.ResourceClassKey = "MyResource"; as well as the resources indicated in the data annotations of the viewmodel classes), the controller or even an ActionFilter is too late to set the culture.

The culture could be set in Application_AcquireRequestState, for example:

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

EDIT

Actually there is a better way using a custom routehandler which sets the culture according to the url, perfectly described by Alex Adamyan on his blog.

All there is to do is to override the GetHttpHandler method and set the culture there.

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);
    }
}
查看更多
孤独总比滥情好
3楼-- · 2019-01-01 06:56

What is the best place is your question. The best place is inside the Controller.Initialize method. MSDN writes that it is called after the constructor and before the action method. In contrary of overriding OnActionExecuting, placing your code in the Initialize method allow you to benefit of having all custom data annotation and attribute on your classes and on your properties to be localized.

For example, my localization logic come from an class that is injected to my custom controller. I have access to this object since Initialize is called after the constructor. I can do the Thread's culture assignation and not having every error message displayed correctly.

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

Even if your logic is not inside a class like the example I provided, you have access to the RequestContext which allow you to have the URL and HttpContext and the RouteData which you can do basically any parsing possible.

查看更多
余欢
4楼-- · 2019-01-01 06:57

1: Create a custom attribute and override method like this:

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, find FilterConfig.cs, add this attribute. (this works for WHOLE application)

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

That's it !

If you want to define culture for each controller/action in stead of whole application, you can use this attribute like this:

[Culture]
public class StudentsController : Controller
{
}

Or:

[Culture]
public ActionResult Index()
{
    return View();
}
查看更多
冷夜・残月
5楼-- · 2019-01-01 07:01

Being as it is a setting that is stored per-user, the session is an appropriate place to store the informtion.

I would change your controller to take the culture string as a parameter, rather than having a different action method for each potential culture. Adding a link to the page is easy, and you shouldn't need to write the same code repeatedly any time a new culture is required.

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>
查看更多
裙下三千臣
6楼-- · 2019-01-01 07:11

I would do it in the Initialize event of the controller like this...

    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;
    }
查看更多
牵手、夕阳
7楼-- · 2019-01-01 07:15

I'm using this localization method and added a route parameter that sets the culture and language whenever a user visits example.com/xx-xx/

Example:

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

I have a filter that does the actual culture/language setting:

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

    }
}

To activate the Internationalization attribute, simply add it to your class:

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

Now whenever a visitor goes to http://example.com/de-DE/Home/Index the German site is displayed.

I hope this answers points you in the right direction.

I also made a small MVC 5 example project which you can find here

Just go to http://{yourhost}:{port}/en-us/home/index to see the current date in English (US), or change it to http://{yourhost}:{port}/de-de/home/index for German etcetera.

查看更多
登录 后发表回答