OK, myślę, że mam to popękane. Zilustruję to scenariuszem uwierzytelniania: chcę asynchronicznie uwierzytelnić użytkownika i użyć wyniku, aby zdecydować, czy zwrócić 401, czy kontynuować z łańcuchem obsługi komunikatów.
Głównym problemem jest to, że nie można wywoływać wewnętrznego programu obsługi SendAsync(), dopóki nie otrzymasz wyniku z asynchronicznego uwierzytelniania.
Kluczem do zrozumienia dla mnie było użycie TaskCompletionSource (TCS) do sterowania przepływem wykonania. Pozwoliło mi to zwrócić zadanie z TCS i ustawić wynik na nim, kiedy tylko mi się podoba - i co najważniejsze, aby opóźnić wywołanie SendAsync(), dopóki nie będę wiedział, że go potrzebuję.
Tak więc skonfigurowałem TCS, a następnie uruchomiłem zadanie autoryzacji. Kontynuując to, patrzę na wynik. Jeśli jest to autoryzowane, przywołuję wewnętrzny łańcuch obsługi i dołączam kontynuację do tego (unikając jakiegokolwiek blokowania gwintów), która uzupełnia TCS. Jeśli uwierzytelnienie się nie powiedzie, wystarczy wypełnić TCS tam, a następnie z 401.
Wynikiem tego jest, że oba asynchroniczne zadania są wykonywane po kolei bez blokowania wątków. Wczytuję to i wygląda na to, że działa dobrze.
To wszystko jest o wiele przyjemniejsze w .NET 4.5 ze składnią async/await ... chociaż podejście z TCS wciąż odbywa się w zasadzie pod kodem, kod jest znacznie prostszy.
Ciesz się!
Pierwszy fragment został zbudowany na platformie .NET 4.0 z interfejsem API WWW Beta - drugi na platformie .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;
}
Oto .NET 4.5/Web API Release Candidate w wersji, która jest dużo bardziej seksownego z nowym asynchroniczny/Oczekujcie składnia:
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);
}
skąd realizowane 'Authorize'? Czy wprowadziłeś nowy 'HttpClient'? – JobaDiniz
Implementacja Authorize nie ma tu zastosowania - jest to tylko funkcja asynchroniczna, która określa, czy żądanie jest dozwolone - sposób wdrożenia go zależy od Ciebie.Można na przykład dokonać sprawdzenia bazy danych, aby sprawdzić, czy bieżący użytkownik ma dostęp do bieżącego żądania w oparciu o pewne niestandardowe reguły biznesowe. –
Po prostu zastanawiałem się, czy możesz utworzyć kolejną prośbę o http, to wszystko ... w moim scenariuszu potrzebuję wykonać inne wywołanie http, przed bieżącym, a ja jestem zaindeksowany innym 'HttpClient'. Zastanawiam się, czy mogę ponownie użyć bieżącego w programie obsługi, ale wygląda na to, że nie mogę – JobaDiniz