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

发布于 2024-08-07 19:54:52 字数 1194 浏览 6 评论 0原文

在 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 管道中是否有更好的位置来执行此操作?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

难忘№最初的完美 2024-08-14 19:54:52

我正在使用这个 本地化方法 并添加每当用户访问 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德国之类的。

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.

记忆之渊 2024-08-14 19:54:52

我知道这是一个老问题,但如果您真的希望将此与您的 ModelBinder 一起使用(相对于 DefaultModelBinder.ResourceClassKey = "MyResource"; 以及数据注释中指示的资源视图模型类),控制器甚至 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);
    }

EDIT

实际上,有一个更好的方法,使用自定义路由处理程序,它根据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);
    }
}

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);
    }
}
家住魔仙堡 2024-08-14 19:54:52

我会在控制器的初始化事件中这样做......

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

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;
    }
世态炎凉 2024-08-14 19:54:52

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

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

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>

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>
方圜几里 2024-08-14 19:54:52

什么是最好的地方是你的问题。最好的地方是在 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 和 HttpContext 以及 RouteData

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.

我是男神闪亮亮 2024-08-14 19:54:52

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

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

  1. 将 VaryByCustom 参数添加到 OutPutCache,如下所示:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    公共 ActionResult 联系()
    {
        返回视图(“联系人”);
    }
    
  2. 在 global.asax.cs 中,使用函数调用从主机获取区域性:

    protected void Application_AcquireRequestState(对象发送者,EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. 将 GetCultureFromHost 函数添加到 global.asax.cs:

    私有 CultureInfo GetCultureFromHost()
    {
        CultureInfo ci = new CultureInfo("en-US"); // en-US
        字符串主机 = 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");
        }
    
        返回ci;
    }
    
  4. 最后重写 GetVaryByCustomString(...) 以也使用此函数:

    公共覆盖字符串 GetVaryByCustomString(HttpContext 上下文,字符串值)
    {
        if (value.ToLower() == "lang")
        {
            CultureInfo ci = GetCultureFromHost();
            返回 ci.Name;
        }
        返回base.GetVaryByCustomString(上下文,值);
    }
    

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

If using Subdomains, for example like "pt.mydomain.com" to set portuguese for example, using Application_AcquireRequestState won't work, because it's not called on subsequent cache requests.

To solve this, I suggest an implementation like this:

  1. Add the VaryByCustom parameter to the OutPutCache like this:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. In global.asax.cs, get the culture from the host using a function call:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. Add the GetCultureFromHost function to 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. And finally override the GetVaryByCustomString(...) to also use this function:

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

The function Application_AcquireRequestState is called on non-cached calls, which allows the content to get generated and cached. GetVaryByCustomString is called on cached calls to check if the content is available in cache, and in this case we examine the incoming host domain value, again, instead of relying on just the current culture info, which could have changed for the new request (because we are using subdomains).

吻泪 2024-08-14 19:54:52

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

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();
}
乖不如嘢 2024-08-14 19:54:52
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;
        }
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;
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文