我有一个自托管的 OWIN 托管 Web API 项目,为我提供了一些基本的REST方法。
我想要多语言错误消息,因此我使用资源文件和 BaseController 来设置 Thread.CurrentCulture 和 Thread.CurrentUICulture 到请求的接受语言标题。
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);
}
一切正常,但如果我将控制器方法设为异步,则会出现问题。
当我在方法中使用等待时,它可能会在另一个线程中继续,因此我的 CurrentCulture 和 CurrentUICulture 会丢失。
这是我用来发现这个问题的一个小例子。
public async Task<HttpResponseMessage> PostData(MyData data)
{
Thread currentThread = Thread.CurrentThread;
await SomeThing();
if (Thread.CurrentThread.CurrentCulture != currentThread.CurrentCulture)
Debugger.Break();
}
我并不总是打破 Debugger.Break 行,但大部分时间都是这样。
以下是我实际使用资源文件的示例。
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 捕获,然后创建响应。
因此,在使用文化之前始终设置文化将是很多代码。
答案 0 :(得分:24)
Joe指出,文化是由ASP.NET中的HttpContext
传递的。 ASP.NET执行此操作的方式是在请求启动时安装SynchronizationContext
,并且该上下文也用于恢复异步方法(默认情况下)。
因此,有两种方法可以解决问题:您可以编写自己的SynchronizationContext
,默认情况下会保留文化,或者您可以在每个await
中明确保留文化。< / p>
要保留每个await
的文化,您可以使用代码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的上下文的一个很好的例子,但一个很好的起点是my MSDN article。
答案 1 :(得分:7)
从.NET 4.5开始,要为所有线程设置默认区域性,请使用:
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
答案 2 :(得分:3)
Thread.CurrentCulture不会跨线程同步。但是,你的HttpContext确实如此。您最好直接从HttpContext获取文化信息。你可以做点什么
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;