1

我有一个在 IIS 8 中的网站下运行的 ASP.NET MVC 5 WEB 应用程序,我需要在运行时通过从数据库读取并存储在会话变量中的用户首选项以编程方式更改语言,并且此值可以在运行时更改带有下拉菜单。

问题是语言资源有时不会改变,尽管 CurrentCulture 确实如此,更糟糕的是它们在应用程序级别冻结,使得所有其他用户会话也冻结到该语言,并且只会在所有应用程序会话中显示相同的语言无论他们的偏好,或者他们甚至试图在运行时更改他们的语言,这都会卡住,这在正常工作时会强制 CurrentThread.CurrentCulture 进行更改。

我在视图中使用隐藏文本来跟踪 CurrentThread.CurrentCulture 是什么以及正在按预期变化,所以问题一定出在其他地方。

这是我用来检查 CurrentCulture 的剃须刀块

<div style="display:none; visibility: hidden;">Thread  
@System.Threading.Thread.CurrentThread.CurrentCulture</div>

在运行时,当语言或 resx 被冻结/卡在应用程序级别时,该值确实是正确的并被选中,但它与视图中显示的语言不匹配,并且在我进行例如更改之前它不会正常工作web.config 或重新启动应用程序,然后它可以正常工作一段时间,但不要持续太长时间。

<div style="display:none; visibility: hidden;">Thread en-US</div>

问题是,这发生在所有用户会话的应用程序级别,并且 CurrentCulture 实际上在应用程序正常工作时按预期发生变化,并且无论何时重新启动,这种情况都不会发生,但不知何故,每次用户会话期间都会触发某些事情以冻结所有当前会话甚至新会话的语言

我也尝试将文化逻辑的更改放在剃须刀视图、动作过滤器和基本控制器中,但这并没有解决问题,以前我有一个基本控制器,我的所有控制器都继承了它,并且 Global.asax.cs 的逻辑要更改并确定文化。

我目前的方法是删除基本控制器并在 Global.asax.cs Application_AcquireRequestState (我也尝试过使用 Application_BeginRequest 但不知何故在生命周期中太早)和操作过滤器中使用。

这是在 Global.asax.cs

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        if (Context.Session != null && Context.Session["CultureName"] != null)
        {
             string cultureName = Context.Session["CultureName"].ToString();

             CultureInfo cultureInfo = new System.Globalization.CultureInfo(cultureName);

            Thread.CurrentThread.CurrentCulture = cultureInfo;
            Thread.CurrentThread.CurrentUICulture = cultureInfo;

        }
    }

我的动作过滤器是这样的

public class CultureActionAttribute : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        string cultureName = null;

        string tenant = filterContext.RouteData.Values["Tenant"] as string;
        if (!String.IsNullOrWhiteSpace(tenant)) 
        {
            TenantService tenantService = new TenantService(tenant);

            cultureName = CultureHelper.GetImplementedCulture(tenantService.Tenant.LanguageCulture);

            tenantService.Unit.Dispose();

        }
        //Logic to override cultureName value by getting the user preference language

        CultureInfo cultureInfo = new System.Globalization.CultureInfo(cultureName);

        Thread.CurrentThread.CurrentCulture = cultureInfo;
        Thread.CurrentThread.CurrentUICulture = cultureInfo;
    }
}

我在 FilterConfig 中添加了它

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new CultureActionAttribute());
        //
    }

我什至尝试在用户更改文化时使用 ClearCachedData。

Thread.CurrentThread.CurrentCulture.ClearCachedData();

我认为这可能与缓存或池回收有关,但我不知道下一步该尝试什么。

我没有在 web.config 中使用任何全球化标签。

在 CurrentThread.CurrentCulture 的视图中跟踪的值始终是正确的,错误的是资源使用冻结。

资源与将它们放置在单独的专用标准文件夹中并使用 PublicResXFileCodeGenerator 将它们公开以能够编译并可供控制器、视图等访问的做法是一致的

资源文件夹

在此处输入图像描述

任何人都可以为我提供另一种选择来尝试或思考我可能做错了什么,或者我还能尝试使资源始终如一地工作。

4

2 回答 2

1

我可以确保获得正确资源的唯一方法是解决 直接覆盖资源的 CurrentThreadUICulture 属性,用于在 CultureFilter 末尾的所有资源查找

Resources.Resource.Culture = cultureInfo;

这成功了!利用只有单个文件作为全局资源,所以现在一切都按预期工作,但我知道这对于多个资源文件和命名空间来说是不方便的。尽管如此,在尝试使用基本控制器和 Iauthorization actionfilter 属性或在 Global.asax.cs 中使用 Application_AcquireRequestState 三种方法都没有的情况下,我没有其他方法可以确保语言更改能够正常工作并且不会冻结尽管 Thread.CurrentThread.CurrentUICulture 始终使用三种方法正确更改,但由于冻结,始终可以正确更改 UI 语言。

于 2016-07-12T03:55:17.210 回答
0

我相信这里发生的事情是您将文化设置在与您实际使用它的地方不同的线程中。您在异步方法中设置文化BeginExecuteCore,这可能不会始终如一地将文化传输到应用程序的 UI 工作线程。这可以解释间歇性行为 - 当应用程序线程运行缓慢时,您可能会在重负载期间看到它“冻结”。

永远不要在 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: "nl"));
        filters.Add(new HandleErrorAttribute());
    }
}

我还认为,使用会话状态进行本地化并不是一个好主意——毕竟,当会话到期时,您将被留在一个黑洞中。

语言环境不是个性化的。语言环境是内容。提供独特内容的正确方法是为其提供一个独特的 URL。如果你这样做,选择文化就像选择一个新的 URL 一样简单,它会自动“坚持”,因为 MVC 重用路由值。即使用户将 URL 发送给其他人,它也永远不会过期。最重要的是,您将网站翻译成多种语言所做的所有工作都将被搜索引擎看到,因此将获得更大的回报。

有关基于 URL 的方法的工作示例,请参阅路由和 url 中的 ASP.NET MVC 5 文化。

于 2016-07-08T21:24:08.213 回答