在ASP.Net MVC应用程序中设置文化


85

在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标头以从其浏览器设置中获取区域性。

编辑:

只是要清楚-我不是要决定是否使用会话。我对此感到满意。我要解决的问题是,最好是在具有针对每个区域性设置操作方法的区域性控制器中执行此操作,还是在MVC管道中有更好的位置执行此操作?


使用会话状态选择用户区域性不是一个好的选择。最好的方法是将区域性包含在URL中,这使得轻松地将当前页面与另一种区域性“交换”。
NightOwl888'6

Answers:


114

我正在使用这种本地化方法,并添加了一个route参数,该参数可在用户访问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]
public class HomeController : Controller {
...

现在,只要访客访问http://example.com/de-DE/Home/Index,就会显示德语站点。

希望以上答案能为您指明正确的方向。

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

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


15
我还喜欢将lang放在URL中,因为它可以被搜索引擎以不同的语言进行抓取,并允许用户使用特定的lang保存或发送URL。
爱德华多·莫尔蒂尼

50
将语言添加到url不会违反REST。实际上,它通过使Web资源不依赖于隐藏的会话状态来遵守该原则。
杰斯·瑞亚

4
Web资源并不依赖于隐藏状态,而是取决于其呈现方式。如果您要访问的资源作为Web服务,你需要选择一种语言做进去。
戴夫·范登Eynde

4
这种类型的解决方案存在一些问题。验证错误消息未翻译。为了解决该问题,我在global.asax.cs文件的Application_AcquireRequestState函数中设置了区域性。
ADH 2015年

4
将其放入过滤器不是一个好主意。模型绑定使用CurrentCulture,但是ActionFilter在模型绑定之后发生。最好在Global.asax,Application_PreRequestHandlerExecute中执行此操作。
Stefan

38

我知道这是一个老问题,但是如果您真的想与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);
    }

编辑

实际上,有一种更好的方法是使用自定义路由处理程序,该处理程序根据URL设置文化,而URL由Alex Adamyan在他的博客中完美描述。

所有要做的就是重写该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。
SkorunkaFrantišek2012年

我读了那个博客。如果我继续使用cookie,是否有任何问题?由于我无权更改。请通知我。这种方法有什么问题吗?
kbvishnu 2012年

@VeeKeyBee如果您的网站是公开的,则在使用Cookie时,所有语言都无法正确索引,对于受保护的网站,您可能还可以。
marapet 2012年

不公开。您能给我一个关于“索引”一词的提示吗?
kbvishnu 2012年

1
您应该问自己的问题并继续阅读SEO,这与原始问题无关。webmasters.stackexchange.com/questions/3786/...
marapet

25

我会在这样的控制器的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;
    }

1
文化字符串不能是const,因为用户需要能够指定他们想在网站上使用的文化。
NerdFury

2
我理解这一点,但问题是在哪里设置文化最好而不是如何设置文化。
杰斯·瑞亚

除了const外,还可以使用类似以下内容的方法:var newCulture = new CultureInfo(RouteData.Values [“ lang”]。ToString());
诺德斯,2010年

AuthorizeCore在OnActionExecuting之前调用,因此您的AuthorizeCore重写方法中将没有任何区域性详细信息。使用控制器的initialize方法可能会更好,特别是在实现自定义AuthorizeAttribute的情况下,因为Initialize方法是在AuthorizeCore之前调用的(在AuthorizeCore中包含区域性详细信息)。
内森·R

7

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

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

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>

感谢您的回答,我没有试图决定是否使用会话。我对此感到满意。我要解决的问题是,最好是在具有针对每个文化设置动作方法的文化控制器中执行此操作,还是在MVC管道中有更好的位置执行此操作
ChrisCa,2009年

我提供了一个更适合该问题的经过编辑的答案。
NerdFury

是的,那肯定更干净,但是我真正想知道的是,是否应该在Controller中完成此操作。或者,如果在MVC管道中有更好的位置来设置文化。或者,如果它在ActionFilters,Handlers,Modules等中更好
ChrisCa,2009年

处理程序和模块没有意义,因为用户没有机会进行选择。您需要一种让用户进行选择,然后处理用户选择的方法,该方法将在控制器中完成。
NerdFury

同意,处理程序和模块应尽早允许用户交互。但是,我对MVC还是很陌生,因此不确定这是否是设置它的最佳途径。如果过一会儿我没听到其他声音,我会接受您的回答。ps,您用来将参数传递给Action方法的语法似乎不起作用。它没有定义控制器,因此仅使用默认控制器(在这种情况下不是正确的控制器)。而且似乎没有其他适合的超载
ChrisCa,2009年

6

最好的地方是你的问题。最好的位置在Controller内部。方法内。MSDN写道,它是在构造函数之后和action方法之前调用的。与重写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和HttpContext以及RouteData,它们基本上可以进行任何解析。


这适用于我的HTML5 Telerik ReportLocalization!。感谢@Patrick Desjardins
CoderRoller 2015年

4

如果使用子域名(例如“ 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来检查内容是否在缓存中可用,在这种情况下,我们再次检查传入的主机域值,而不是仅依赖当前的区域性信息,因为新的请求可能已经更改了该信息(因为我们正在使用子域)。


4

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

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
请解释为什么这应该是最好的方法。
Max Leske
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.