OK、私はこれにひびが入ったと思います。認証シナリオでこれを説明しています。ユーザーを非同期的に認証し、その結果を使用して 401 を返すか、メッセージ ハンドラー チェーンを続行するかを決定します。
中心的な問題は、非同期認証の結果が得られるまで、内部ハンドラー SendAsync() を呼び出せないことです。
私にとって重要な洞察は、TaskCompletionSource (TCS) を使用して実行の流れを制御することでした。これにより、TCS から Task を返し、好きなときに結果を設定できるようになりました。最も重要なことは、必要になるまで SendAsync() の呼び出しを遅らせることでした。
そこで、TCS をセットアップしてから、承認を行うタスクを開始しました。この続きで、結果を見ていきます。許可されている場合は、内部のハンドラー チェーンを呼び出し、TCS を完了する(スレッドのブロックを回避する)継続をこれにアタッチします。認証が失敗した場合は、そこで TCS を完了してから 401 を送信します。
この結果、両方の非同期タスクがスレッドのブロックなしで順番に実行されます。これを負荷テストしたところ、問題なく動作するようです。
ただし、async/await 構文を使用する .NET 4.5 ではすべてがはるかに優れています... TCS を使用したアプローチは基本的にまだ内部で行われていますが、コードははるかに単純です。
楽しみ!
最初のスニペットは Web API Beta を使用して .NET 4.0 で構築され、2 番目のスニペットは .NET 4.5/Web API RC で構築されました。
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
// Authorize() returns a started
// task that authenticates the user
// if the result is false we should
// return a 401 immediately
// otherwise we can invoke the inner handler
Task<bool> authenticationTask = Authorize(request);
// attach a continuation...
authenticationTask.ContinueWith(_ =>
{
if (authenticationTask.Result)
{
// authentication succeeded
// so start the inner handler chain
// and write the result to the
// task completion source when done
base.SendAsync(request, cancellationToken)
.ContinueWith(t => taskCompletionSource.SetResult(t.Result));
}
else
{
// authentication failed
// so complete the TCS immediately
taskCompletionSource.SetResult(
new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
});
return taskCompletionSource.Task;
}
これは、新しい async/await 構文を備えた、より魅力的な .NET 4.5 / Web API Release Candidate バージョンです。
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Authorize still has a Task<bool> return type
// but await allows this nicer inline syntax
var authorized = await Authorize(request);
if (!authorized)
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("Unauthorized.")
};
}
return await base.SendAsync(request, cancellationToken);
}