11

我有一个面向 .NET Framework 4.7.1 的 ASP.NET MVC 4 应用程序,如果操作包含异步调用,则控制器和视图之间不共享区域性的问题。

我引用的是 NuGet 包Microsoft.AspNet.Mvc5.2.3(并且可以在 5.2.4 中复制)。

这是控制器中的代码:

public class CulturesTestController : Controller
{
    public async Task<ActionResult> Index(string value)
    {
        Thread.CurrentThread.CurrentCulture = 
            CultureInfo.GetCultureInfo("fi-FI");
        Thread.CurrentThread.CurrentUICulture = 
            CultureInfo.GetCultureInfo("fi-FI");
        var model = new CulturesContainer
        {
            CurrentCulture = Thread.CurrentThread.CurrentCulture,
            CurrentUICulture = Thread.CurrentThread.CurrentUICulture,
            CurrentThreadId = Thread.CurrentThread.ManagedThreadId
        };
        Log.Write(Level.Info, "CurrentUICulture - Before Await - " +
                              "CurrentCulture: " +
                              $"{Thread.CurrentThread.CurrentCulture}, " +
                              "CurrentUICulture: "
                              ${Thread.CurrentThread.CurrentUICulture} -> "+
                              "ThreadId: " + 
                              $"{Thread.CurrentThread.ManagedThreadId}");

        await GetAwait();
        Log.Write(Level.Info, "CurrentUICulture - After Await - " +
                              "CurrentCulture: " + 
                              $"{Thread.CurrentThread.CurrentCulture}, " +
                              "CurrentUICulture: " +
                              $"{Thread.CurrentThread.CurrentUICulture} -> " +
                              "ThreadId: " +
                              $"{Thread.CurrentThread.ManagedThreadId}");
        return View("Index", model);
    }

    public class CulturesContainer
    {
        public CultureInfo CurrentCulture { get; set; }
        public int CurrentThreadId { get; set; }
        public CultureInfo CurrentUICulture { get; set; }
    }

    private async Task GetAwait()
    {
        await Task.Yield();
    }
}

这是视图中的代码:

@using System.Globalization
@using System.Threading
@model CultureTest.Controllers.CulturesTestController.CulturesContainer
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>title</title>
</head>
<body>
    <div>
        InitialCurrentCulture = {
        <label>@Html.Raw(Model.CurrentCulture)</label>
        }  --
        InitialCurrentUICulture = {
        <label>@Html.Raw(Model.CurrentUICulture)</label>
        }  --
        InitialThreadId = {
        <label>@Html.Raw(Model.CurrentThreadId)</label>
        }
        <br />
        ActualCurrentCulture = {
        <label>@Html.Raw(CultureInfo.CurrentCulture)</label>
        }  --
        ActualCurrentUICulture = {
        <label>@Html.Raw(CultureInfo.CurrentUICulture)</label>
        }  --
        ActualThreadId = {
        <label>@Html.Raw(Thread.CurrentThread.ManagedThreadId)</label>
        }
    </div>
</body>
</html>

日志如下:

20180320-12:04:25.357-12   -INFO -CulturesTestController+<Index>d__0: 
    CurrentUICulture - Before Await - 
    CurrentCulture: fi-FI, CurrentUICulture: fi-FI -> ThreadId: 12
20180320-12:04:25.357-8    -INFO -CulturesTestController+<Index>d__0: 
    CurrentUICulture - After Await - 
    CurrentCulture: fi-FI, CurrentUICulture: fi-FI -> ThreadId: 8

当网页显示:

InitialCurrentCulture = { fi-FI } -- 
InitialCurrentUICulture = { fi-FI } -- InitialThreadId = { 12 } 
ActualCurrentCulture = { en-US } --
ActualCurrentUICulture = { en-US } -- ActualThreadId = { 9 } 

.NET Framework < 4.6 中存在一个问题,该问题已在 4.6 中修复,但似乎该问题在 MVC 中仍然存在。

如果线程是执行基于任务的异步操作的线程池线程,并且应用面向 .NET Framework 4.6 或更高版本的 .NET Framework,则其 UI 文化由调用线程的 UI 文化决定。(来源https://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.currentuiculture(v=vs.110).aspx

我是否没有正确处理CurrentCulture,或者这就是它应该如何工作?对于 .NET Framework 4.x,我找不到与此问题相关的任何帖子。

4

1 回答 1

6

我是否没有正确处理 CurrentCulture,或者这就是它应该如何工作?

你不是。在进入异步操作方法之前,需要设置当前 UI 线程的文化。如果您在异步方法内部设置文化,它对 UI 线程没有影响(正如您所发现的)。

但是除了异步问题之外,在MVC的应用程序生命周期中设置动作方法内部的文化为时已晚,因为 MVC 的一些重要的文化敏感特性(即模型绑定)在此之前运行。

在生命周期中尽早设置文化的一种方法是使用授权过滤器,它确保在模型绑定发生之前设置文化。

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

public class CultureFilter : IAuthorizationFilter
{
    private readonly string defaultCulture;

    public CultureFilter(string defaultCulture)
    {
        this.defaultCulture = defaultCulture;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var values = filterContext.RouteData.Values;

        string culture = (string)values["culture"] ?? this.defaultCulture;

        CultureInfo ci = new CultureInfo(culture);

        Thread.CurrentThread.CurrentCulture = ci;
        Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name);
    }
}

并在全球范围内注册:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CultureFilter(defaultCulture: "fi-FI"));
        filters.Add(new HandleErrorAttribute());
    }
}

上面的示例假设您已设置路由以添加culture类似于此答案的路由值,但如果需要,您可以设计一个过滤器以从其他地方获取文化。

注意:我意识到这个问题与 .NET Core 无关,但正如@GSerg 在评论中指出的那样,同样的问题被报告为 ASP.NET Core 的一个错误,并且它被关闭为by design. 他们得出的结论是一样的:

在资源过滤器中设置区域性将适用于操作和视图。

于 2018-03-20T12:36:12.937 回答