【问题标题】:Set Culture in an ASP.Net MVC app在 ASP.Net MVC 应用程序中设置文化
【发布时间】:2010-12-06 08:34:27
【问题描述】:

在 ASP.net MVC 应用程序中设置文化/UI 文化的最佳位置是什么

目前我有一个 CultureController 类,如下所示:

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

以及主页上每种语言的超链接,链接如下:

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

效果很好,但我认为有更合适的方法来做到这一点。

我正在使用以下 ActionFilter 阅读文化 http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx。我有点像 MVC 菜鸟,所以我不确定我是否将其设置在正确的位置。我不想在 web.config 级别这样做,它必须基于用户的选择。我也不想检查他们的 http-headers 以从他们的浏览器设置中获取文化。

编辑:

要明确一点——我并不是要决定是否使用会话。我对那一点很满意。我想要解决的是最好在具有每个 Culture 设置的操作方法的 Culture 控制器中执行此操作,还是在 MVC 管道中有更好的位置来执行此操作?

【问题讨论】:

  • 使用会话状态来选择用户文化不是一个好的选择。最好的方法是include the culture as part of the URL,这样可以很容易地用另一种文化“交换”当前页面。

标签: asp.net-mvc localization culture


【解决方案1】:

我正在使用这个localization method 并添加了一个路由参数,用于在用户访问 example.com/xx-xx/ 时设置文化和语言

例子:

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

我有一个过滤器可以进行实际的文化/语言设置:

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

    }
}

要激活 Internationalization 属性,只需将其添加到您的类中:

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

现在,每当访问者访问 http://example.com/de-DE/Home/Index 时,就会显示德国网站。

我希望这个答案可以为您指明正确的方向。

我还制作了一个小型 MVC 5 示例项目,您可以找到 here

只需转到 http://{yourhost}:{port}/en-us/home/index 以查看英语(美国)的当前日期,或将其更改为 http://{yourhost}:{port }/de-de/home/index 用于德语等。

【讨论】:

  • 我也喜欢将语言放在 URL 中,因为它可以被不同语言的搜索引擎抓取,并允许用户保存或发送具有特定语言的 URL。
  • 将语言添加到 url 不会违反 REST。事实上,它通过使网络资源不依赖于隐藏的会话状态来遵守它。
  • 网络资源不依赖于隐藏状态,它的呈现方式是。如果您想以 Web 服务的形式访问资源,则需要选择一种语言。
  • 我在使用这种类型的解决方案时遇到了一些问题。验证错误消息没有被翻译。为了解决这个问题,我在 global.asax.cs 文件的 Application_AcquireRequestState 函数中设置了文化。
  • 把它放在过滤器中不是一个好主意。模型绑定使用 CurrentCulture,但 ActionFilter 发生在模型绑定之后。最好在 Global.asax,Application_PreRequestHandlerExecute 中执行此操作。
【解决方案2】:

我知道这是一个老问题,但如果您真的希望使用您的 ModelBinder(关于 DefaultModelBinder.ResourceClassKey = "MyResource"; 以及 viewmodel 类的数据注释中指示的资源),控制器甚至ActionFilter 都来不及设置文化

文化可以设置在Application_AcquireRequestState,例如:

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

编辑

实际上有一个更好的方法使用 custom routehandler,它根据 url 设置文化,Alex Adamyan on his blog 完美描述。

所有要做的就是覆盖 GetHttpHandler 方法并在那里设置文化。

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

【讨论】:

  • 不幸的是,RouteData 等在“Application_AcquireRequestState”方法中不可用,但它们在 Controller.CreateActionInvoker() 中。所以我建议“保护覆盖 IActionInvoker CreateActionInvoker()”并在那里设置 CultureInfo。
  • 我读过那个博客。如果我继续使用 cookie 有什么问题吗?因为我没有更改它的权限。请通知我。这种方法有什么问题吗?
  • @VeeKeyBee 如果您的网站是公开的,那么在使用 cookie 时,所有语言都不会被正确索引,对于受保护的网站,您可能没问题。
  • 不是不公开的。能否请您提示一下“索引”这个词?
  • 您应该提出自己的问题并阅读 SEO,这与原始问题无关。 webmasters.stackexchange.com/questions/3786/…
【解决方案3】:

我会在控制器的 Initialize 事件中这样做...

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

【讨论】:

  • 文化字符串不能是常量,因为用户需要能够指定他们想在网站上使用的文化。
  • 我明白这一点,但问题是最好在哪里设置文化而不是如何设置。
  • 你可以使用类似的东西来代替 const: var newCulture = new CultureInfo(RouteData.Values["lang"].ToString());
  • AuthorizeCore 在 OnActionExecuting 之前被调用,因此您的 AuthorizeCore 覆盖方法中不会有任何文化细节。使用控制器的 initialize 方法可能会更好,尤其是在您实现自定义 AuthorizeAttribute 时,因为 Initialize 方法在 AuthorizeCore 之前调用(您将在 AuthorizeCore 中获得文化细节)。
【解决方案4】:

由于它是按用户存储的设置,因此会话是存储信息的合适位置。

我会更改您的控制器以将区域性字符串作为参数,而不是为每种潜在的区域性设置不同的操作方法。向页面添加链接很容易,您无需在需要新文化时重复编写相同的代码。

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>

【讨论】:

  • 感谢您的回答,我不想决定是否使用会话。我对那一点很满意。我想要解决的是,如果最好在具有每个 Culture 设置的操作方法的 Culture 控制器中执行此操作,或者 MVC 管道中是否有更好的位置来执行此操作
  • 我提供了一个更适合问题的编辑答案。
  • 是的,这当然更干净,但我真正想知道的是这是否应该在控制器中完成。或者,如果 MVC 管道中有更好的位置来设置文化。或者如果它在 ActionFilters、Handlers、Modules 等中更好
  • 处理程序和模块没有意义,因为用户没有机会做出选择。您需要一种方法让用户做出选择,然后处理用户的选择,这将在控制器中完成。
  • 同意,处理程序和模块要尽早允许用户交互。但是,我对 MVC 很陌生,所以不确定这是否是管道中设置它的最佳位置。如果一段时间后我没有听到其他消息,我会接受你的回答。 p.s.您用来将参数传递给 Action 方法的语法似乎不起作用。它没有定义控制器,所以只使用默认的控制器(在这种情况下不是正确的控制器)。而且似乎没有另一个适合的重载
【解决方案5】:

什么是最好的地方是你的问题。最好的地方是 Controller.Initialize 方法。 MSDN 写道,它是在构造函数之后和操作方法之前调用的。与重写 OnActionExecuting 不同,将代码放在 Initialize 方法中可以让您受益于在类和要本地化的属性上拥有所有自定义数据注释和属性。

例如,我的本地化逻辑来自一个注入到我的自定义控制器的类。我可以访问这个对象,因为 Initialize 在构造函数之后被调用。我可以进行线程的文化分配,但不能正确显示每条错误消息。

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

即使您的逻辑不在我提供的示例中的类中,您也可以访问 RequestContext,它允许您拥有 URL 和 HttpContextRouteData,您基本上可以进行任何可能的解析。

【讨论】:

  • 这适用于我的 HTML5 Telerik ReportLocalization!。谢谢@Patrick Desjardins
【解决方案6】:

如果使用子域,例如“pt.mydomain.com”来设置葡萄牙语,则使用 Application_AcquireRequestState 将不起作用,因为它不会在后续缓存请求中调用。

为了解决这个问题,我建议这样的实现:

  1. 像这样将 VaryByCustom 参数添加到 OutPutCache:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. 在 global.asax.cs 中,使用函数调用从主机获取文化:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. 将 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. 最后重写 GetVaryByCustomString(...) 以也使用此函数:

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

函数 Application_AcquireRequestState 在非缓存调用上被调用,它允许生成和缓存内容。在缓存调用中调用 GetVaryByCustomString 以检查内容是否在缓存中可用,在这种情况下,我们再次检查传入的主机域值,而不是仅依赖当前的文化信息,这可能会因新请求而改变(因为我们正在使用子域)。

【讨论】:

    【解决方案7】:

    1:创建自定义属性和覆盖方法,如下所示:

    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:在App_Start中,找到FilterConfig.cs,添加这个属性。 (这适用于整个应用程序)

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

    就是这样!

    如果你想为每个控制器/动作而不是整个应用程序定义文化,你可以像这样使用这个属性:

    [Culture]
    public class StudentsController : Controller
    {
    }
    

    或者:

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

    【讨论】:

      【解决方案8】:
      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;
              }
      

      【讨论】:

      • 请解释为什么这应该是最好的方法。
      猜你喜欢
      • 1970-01-01
      • 2015-06-12
      • 1970-01-01
      • 2013-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多