多语言ASP.NET MVC网站设置CurrentCulture的最佳位置

43

我正在为多语言ASP.NET MVC 3 web应用程序确定控制器工厂中的Thread.CurrentThread.CurrentCultureThread.CurrentThread.CurrentUICulture

public class MyControllerFactory : DefaultControllerFactory {

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) {

        //Get the {language} parameter in the RouteData
        string UILanguage;
        if (requestContext.RouteData.Values["language"] == null)
            UILanguage = "tr";
        else
            UILanguage = requestContext.RouteData.Values["language"].ToString();

        //Get the culture info of the language code
        CultureInfo culture = CultureInfo.CreateSpecificCulture(UILanguage);
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;

        return base.GetControllerInstance(requestContext, controllerType);
    }

}

上面的代码现在已经近一年了!所以,我很乐意听取建议。

我将其注册到Global.asax文件中,如下所示:

ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
这很有效,但我不确定这是否是最佳实践和执行此类型操作的最佳位置。
我还没有深入了解ControllerFactory的主要角色,也无法将其与ActionFilterAttribute进行比较。
您认为执行此类型操作的最佳位置是什么?

3
我还没有找到一个很好的理由来改变当前文化。虽然它可以自动处理日期和时间格式,但我认为仅为此目的而改变文化有些过分(例如,在连接使用文化Y的服务器时,您想要线程文化为X吗?我宁愿保持CurrentCulture不变,并在需要格式化日期和数字时明确地使用用户文化。(在我看来,CurrentUICulture是可以接受的。) - Hector Correa
@HectorCorrea 谢谢Hector。其实我从来没有想过这个问题,最好的部分是你是对的。我只是为资源文件更改它。你认为CurrentUICulture就足够了吗? - tugberk
2
是的,CurrentUICulture 对于资源来说已经足够了。[有趣的是,我在今年早些时候切换到土耳其文化时,使用 CurrentCulture 时被大写字母“i”和小写字母“ı”的问题所困扰。这与此链接中描述的非常相似。我想知道你对此有何看法,因为你似乎处理土耳其语言。http://www.west-wind.com/weblog/posts/2005/May/23/DataRows-String-Indexes-and-case-sensitivity-with-Turkish-Locale] - Hector Correa
@HectorCorrea 嗯,是的。即使我只玩UICulture,也会发生吗? - tugberk
1
不,它与UICulture一起正常工作。 - Hector Correa
2
我知道这个问题很老,但我必须反驳一下。CurrentCulture是可以被操纵的。需要明确设置对数据库等的写入(例如使用InvariantCulture)。如果依赖CurrentCulture,就会遇到麻烦。 - Rich
5个回答

57

我曾经使用全局的ActionFilter来设置语言环境,但最近我意识到,在一些情况下在OnActionExecuting方法中设置当前文化信息太晚了。例如,当模型在POST请求后传递到控制器时,ASP.NET MVC会为模型创建元数据。这发生在任何操作执行之前。因此,此时处理DisplayName属性值和其他数据注释时,使用的是默认的文化信息。

最终,我将设置当前文化信息移动到自定义的IControllerActivator实现中,并且它非常有效。我认为从请求生命周期的角度来看,将此逻辑托管在自定义控制器工厂中几乎与今天的方式相同。与使用全局的ActionFilter相比,更加可靠。

CultureAwareControllerActivator.cs:

public class CultureAwareControllerActivator: IControllerActivator
{
    public IController Create(RequestContext requestContext, Type controllerType)
    {
        //Get the {language} parameter in the RouteData
        string language = requestContext.RouteData.Values["language"] == null ?
            "tr" : requestContext.RouteData.Values["language"].ToString();

        //Get the culture info of the language code
        CultureInfo culture = CultureInfo.GetCultureInfo(language);
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;

        return DependencyResolver.Current.GetService(controllerType) as IController;
    }
}

Global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        ...
        ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CultureAwareControllerActivator()));
    }
}

@shanabus 很抱歉,我已经没有这段代码了,但我尝试重构它并更新了答案中的代码片段。我不确定 CultureAwareControllerActivator 的注册是否正确,但它可以工作。 - s.ermakovich
1
此时,RouteData 尚未包含任何操作参数。但是,在基于 URL 的固定部分(主机名或 URL 路径的第一部分)的文化设置时仍然很有用。 - marapet
为什么你不想在onRequestBegin事件中执行这个行为呢?在我看来,那样做可以覆盖所有可能的情况。 - Agile Jedi
@AgileJedi OnBeginRequest并不提供有关请求(路由数据等)的任何信息。它只是一个新请求创建的信号。因此,在这种情况下,文化选择的能力非常有限。 - s.ermakovich
2
ActionFilters 在某些情况下(如果不是所有情况)确实太晚了,主要是因为模型绑定(在此处会将字符串转换为数字和日期等)在 Action Filters 之前发生。 - Jeff

7

我知道已经选择了一个答案。我们使用的选项是在应用程序的OnBeginRequest事件中初始化线程当前区域设置。这可以确保每个请求都能发现区域设置。

public void OnBeginRequest(object sender, EventArgs e)
{
   var culture = YourMethodForDiscoveringCulutreUsingCookie();
   System.Threading.Thread.CurrentThread.CurrentCulture = culture;
   System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
}

我们有在BeginRequest方法中设置文化的选项。我们还可以在AcquireRequestState方法中设置状态和特定于文化的信息。BeginRequest和AcquireRequestState方法都会为每个请求执行。BeginRequest首先执行,然后是AcquireRequestState。 - ssmsnet

4

谢谢!这也是我考虑过的选项。我主要在寻找最佳实践。使用ActionFilterAttribute执行此操作的优缺点是什么? - tugberk
1
+1,这就是正确的做法。这是我制作的一个示例属性:https://github.com/jgauffin/griffin.mvccontrib/blob/master/source/Griffin.MvcContrib/Localization/LocalizedAttribute.cs - jgauffin
@jgauffin 加入CookieName属性是一个好主意。 不过你将其设为不可更改的。 如果属性在另一个项目中,拥有这个选项是很好的。 - tugberk
@jgauffin 我发现通过 ActionFilter 进行本地化应用的缺点。如果你缓存你的操作而缓存仍然有效,那么 ActionFilter 就不会执行。最好的方式是设置一个新的控制器工厂进行本地化。 - tugberk
@tugberk:不是的。你需要指定缓存应该按参数变化。(使用OutputCache属性+global.asax/GetVaryByCustomString) - jgauffin
@jgauffin,通过设置新的控制器工厂来实现这个功能真的很糟糕吗? - tugberk

3

你可以不需要覆写 OnActionExecuting 方法而是在这里覆写 Initialize 方法,像这样:

protected override void Initialize(RequestContext requestContext)
{
        string culture = null;
        var request = requestContext.HttpContext.Request;
        string cultureName = null;

        // Attempt to read the culture cookie from Request
        HttpCookie cultureCookie = request.Cookies["_culture"];
        if (cultureCookie != null)
            cultureName = cultureCookie.Value;
        else
            cultureName = request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

        // Validate culture name
        cultureName = CultureHelper.GetValidCulture(cultureName); // This is safe

        if (request.QueryString.AllKeys.Contains("culture"))
        {
            culture = request.QueryString["culture"];
        }
        else
        {
            culture = cultureName;
        }

        Uitlity.CurrentUICulture = culture;

        base.Initialize(requestContext);
    }

谢谢分享。设置CurrentUICulture的Utility类是什么样子的? - shanabus
实用程序类将具有以下定义的属性 code public static string CurrentUICulture { get { return Thread.CurrentThread.CurrentUICulture.Name; } set { Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(CultureHelper.GetValidCulture(value)); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(CultureHelper.GetValidCulture(value)); } } - Hitendra

0

如果你不使用ControllerActivator,可以使用BaseController类并从它继承。

public class BaseController : Controller
{
    public BaseController()
    {
          //Set CurrentCulture and CurrentUICulture of the thread
    }
}

public class HomeController: BaseController    
{
    [HttpGet]
    public ActionResult Index()
    {
        //..............................................
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接