问题描述
我有一个自托管的 OWIN 托管的 Web API 项目,它为我提供了一些基本的 REST 方法.
I have a self-hosted OWIN hosted Web API project providing some basic REST methods for me.
我想要多语言错误消息,所以我使用Resource文件和一个BaseController来设置Thread.CurrentCulture和Thread.CurrentUICulture 到请求的 Accept-Language 标头.
I want to have multilingual error messages, so I use Resource files and a BaseController that sets the Thread.CurrentCulture and Thread.CurrentUICulture to the Accept-Language header of the request.
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);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
base.ExecuteAsync(controllerContext, cancellationToken);
}
一切正常,但如果我使我的控制器方法异步,问题就会出现.
That all works nice, but the problem appears if I make my controller methods async.
当我在方法中使用 await 时,它可能会在另一个线程中继续,因此我的 CurrentCulture 和 CurrentUICulture 丢失了.
When I use await in the method, it might continue in another thread, and so my CurrentCulture and CurrentUICulture are lost.
这是我用来发现这个问题的一个小例子.
Here's an little example I used to find this issue.
public async Task<HttpResponseMessage> PostData(MyData data)
{
Thread currentThread = Thread.CurrentThread;
await SomeThing();
if (Thread.CurrentThread.CurrentCulture != currentThread.CurrentCulture)
Debugger.Break();
}
我并不总是在 Debugger.Break 行中打断,但大多数时候我都会这样做.
I don't always break in the Debugger.Break line, but most of the time I do.
这是我实际使用我的资源文件的示例.
Here's an example where I actually use my Resource File.
public async Task<HttpResponseMessage> PostMyData(MyData data)
{
//Before this if I'm in the correct thread and have the correct cultures
if (await this._myDataValidator.Validate(data) == false)
//However, I might be in another thread here, so I have the wrong cultures
throw new InvalidMyDataException();
}
public class InvalidMyDataException : Exception
{
public InvalidMyDataException()
//Here I access my resource file and want to get the error message depending on the current culture, which might be wrong
: base(ExceptionMessages.InvalidMyData)
{
}
}
一些附加信息:我有一大堆这样的异常,它们都被自定义ExceptionFilterAttribute捕获,然后创建响应.
Some additional information: I have a whole bunch of exceptions like this, and they all get caught in an custom ExceptionFilterAttribute which then creates the response.
所以在我使用它之前总是正确设置文化将是很多代码.
So it would be much code to always set the culture right before I use it.
推荐答案
正如 Joe 所指出的,文化是由 ASP.NET 中的 HttpContext
传递的.ASP.NET 执行此操作的方式是在请求开始时安装 SynchronizationContext
,并且该上下文还用于恢复异步方法(默认情况下).
As Joe pointed out, culture is transferred by the HttpContext
in ASP.NET. The way ASP.NET does this is by installing a SynchronizationContext
when a request starts, and that context is also used to resume asynchronous methods (by default).
因此,有几种方法可以解决这个问题:您可以编写自己的 SynchronizationContext
来默认保留文化,或者您可以在每个 await 中显式保留文化
.
So, there are a couple of ways to approach the problem: you can either write your own SynchronizationContext
that will preserve culture by default, or you can explicitly preserve the culture across each await
.
要在每个 await
保留文化,您可以使用代码 来自斯蒂芬·图布:
To preserve the culture at each await
, you can use code from Stephen Toub:
public static CultureAwaiter WithCulture(this Task task)
{
return new CultureAwaiter(task);
}
public class CultureAwaiter : INotifyCompletion
{
private readonly TaskAwaiter m_awaiter;
private CultureInfo m_culture;
public CultureAwaiter(Task task)
{
if (task == null) throw new ArgumentNullException("task");
m_awaiter = task.GetAwaiter();
}
public CultureAwaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return m_awaiter.IsCompleted; } }
public void OnCompleted(Action continuation)
{
m_culture = Thread.CurrentThread.CurentCulture;
m_awaiter.OnCompleted(continuation);
}
public void GetResult()
{
Thread.CurrentThread.CurrentCulture = m_culture;
m_awaiter.GetResult();
}
}
SynchronizationContext
方法比较复杂,但是一旦设置好,就会更容易使用.我不知道 ASP.NET 类上下文的一个很好的例子,但一个好的起点是 我的 MSDN 文章.
The SynchronizationContext
approach is more complicated but once it's set up, it will be easier to use. I don't know of a good example of an ASP.NET-like context, but a good starting point is my MSDN article.
这篇关于异步 WebApi Thread.CurrentCulture的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!