自己ホスト型[〜#〜] owin [〜#〜]ホスト型Web APIいくつかの基本的なRESTメソッドを提供するプロジェクト私。
多言語のエラーメッセージを表示したいので、ResourceファイルとBaseControllerを使用してThread.CurrentCultureとThread。 CurrentUICultureAccept-Languageリクエストのヘッダーに。
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);
}
すべてうまくいきますが、controller methods asyncにすると問題が発生します。
メソッドでawaitを使用すると、別のスレッドで続行される可能性があるため、-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行で常に改行するとは限りませんが、ほとんどの場合は改行します。
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に巻き込まれ、応答が作成されます。
したがって、使用する直前にカルチャを常に設定することは、多くのコードになります。
Joeが指摘したように、文化はASP.NETのHttpContext
によって転送されます。 ASP.NETがこれを行う方法は、リクエストの開始時にSynchronizationContext
をインストールすることであり、そのコンテキストは非同期メソッドの再開にも使用されます(デフォルト)。
したがって、問題に対処する方法はいくつかあります。デフォルトでカルチャを保持する独自のSynchronizationContext
を作成するか、各await
全体でカルチャを明示的に保持できます。
各await
でカルチャーを保持するには、コード 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記事 です。
.NET 4.5以降、すべてのスレッドにデフォルトのカルチャを設定するには、以下を使用します。
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
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;