web-dev-qa-db-ja.com

非同期WebApi Thread.CurrentCulture

自己ホスト型[〜#〜] owin [〜#〜]ホスト型Web APIいくつかの基本的なRESTメソッドを提供するプロジェクト私。

多言語のエラーメッセージを表示したいので、ResourceファイルとBaseControllerを使用してThread.CurrentCultureThread。 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を使用すると、別のスレッドで続行される可能性があるため、-CurrentCultureCurrentUICultureが失われます。

これは私がこの問題を見つけるために使用した小さな例です。

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に巻き込まれ、応答が作成されます。

したがって、使用する直前にカルチャを常に設定することは、多くのコードになります。

40
Daniel Häfele

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記事 です。

25
Stephen Cleary

.NET 4.5以降、すべてのスレッドにデフォルトのカルチャを設定するには、以下を使用します。

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
7
Mike Fuchs

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;
3
Joe Enzminger