使用OWIN和异步方法实现多语言网站

12

背景

我正在使用ASP.NET 4.6、C#、OWIN管道和IIS (Microsoft.Owin.Host.SystemWeb)创建一个简单的多语言网站,它有许多异步方法调用和标准的全局资源文件(App_GlobalResources中的*.resx)。该网站使用MVC5、WebAPI2和Autofac作为依赖项解析器。

问题

由于异步方法使用每个请求的多个线程,我无法正确更改生成页面的区域设置/文化,因为我找不到一种方法来为与给定请求相关联的每个线程设置Thread.Current[UI]Culture,因为这些属性不是同步的。我还希望保持干净的代码,而不是将“async/await culture configuration”混淆在有用的代码中。

代码

Startup.cs

public void Configuration(IAppBuilder app)
{
    ...

    app.UseAutofacMiddleware(container);
    app.UseAutofacMvc();
    app.UseAutofacWebApi(httpConfiguration);
    app.UseWebApi(httpConfiguration);

    ...

    app.Use(async (ctx, next) =>
    {
        /* in production, detect based on current URL and/or cookie */
        var culture = new CultureInfo("pl_PL");

        CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = culture;

        await next.Invoke();
    });
}

SampleController.cs

public async Task<ActionResult> SayHello()
{
    // returns pl_PL message
    var msgA = Resources.Messages.HelloWorld; 

    await someService.doSthAsync();

    // returns system default (en_US) message
    var msgB = Resources.Messages.HelloWorld;

    return Content(msgA + " - " + msgB);
}

  1. 我是否应该根据这个StackOverflow答案的建议创建一个自定义[AspNet]SynchronizationContext?如果是这样,我应该如何做?
  2. 我是否应该放弃使用全局资源作为翻译源,转而使用其他方法?如果是,我可以使用什么(库)?

你可以在操作方法开始处保存当前的文化。SampleController 继承自 Controller 类,因此你可以从那里获取当前的 HTTP 上下文和文化。 - Paulo Morgado
我可以在操作方法的开头检测到文化,但是我应该在哪里分配它以便在用于服务单个HTTP响应的多个线程中保留它? - Crozin
你有看过你发的链接上的代码吗? - Paulo Morgado
是的,我做过,但它需要额外的“肮脏”代码,我想避免这种情况。它还建议改变同步上下文的实现,但我不太确定如何正确地做到这一点。 - Crozin
代码不是用于同步上下文的实现。这段代码类似于EntityFramework为ASP.NET Identity 2所使用的代码。 - Paulo Morgado
2个回答

5
使用Owing Globalization Library怎么样?它似乎是专门为此创建的。
您仍然可以使用resx来本地化资源,并且具有出色的自定义功能:
public void Configuration(IAppBuilder app)
{
    ...
    app.UseGlobalization(new OwinGlobalizationOptions("en-US",true)
       .DisablePaths("Content", "bundles")
       .Add("fr-FR", true).AddCustomSeeker(new CultureFromPreferences()));
}

我已经测试了async/await的使用,文化得到保留:

    public async Task<ActionResult> About()
    {
        var msgA = Resources.Resources.About;

        await Task.Delay(1000);

        var msgB = Resources.Resources.About;

        ViewBag.Message = msgA + " - " + msgB;

        return View();
    }

注意:我不是该库的作者,只是之前曾经使用过它。

1

Joe Enzmiger的回答看起来很合适

public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext     controllerContext, CancellationToken cancellationToken)
{
if (controllerContext.Request.Headers.AcceptLanguage != null && 
    controllerContext.Request.Headers.AcceptLanguage.Count > 0)
{
    string language = controllerContext.Request.Headers.AcceptLanguage.First().Value;
    var culture = CultureInfo.CreateSpecificCulture(language);
    HttpContext.Current.Items["Culture"] = culture;
    //Thread.CurrentThread.CurrentCulture = culture;
    //Thread.CurrentThread.CurrentUICulture = culture;
}

base.ExecuteAsync(controllerContext, cancellationToken); 
}

然后,在任何需要文化的任务中,您都需要它:
var culture = HttpContext.Current != null ? HttpContext.Current.Items["Culture"] as CultureInfo : Thread.CurrentThread.CurrentCulture;

2
当你从另一个用户那里抄袭答案时,礼貌的做法是链接到原始答案。 - Julien Lebot

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