OK, penso di averlo rotto. Sto illustrando questo con uno scenario di autenticazione: voglio autenticare un utente in modo asincrono e utilizzare il risultato per decidere se restituire un 401 o continuare con la catena di gestione dei messaggi.
Il problema centrale è che non è possibile chiamare il gestore interno SendAsync() finché non si ottiene il risultato dell'autenticazione asincrona.
L'intuizione chiave per me era utilizzare un TaskCompletionSource (TCS) per controllare il flusso di esecuzione. Ciò mi ha permesso di restituire l'attività dal TCS e di impostare un risultato su di esso ogni volta che mi piaceva, e soprattutto di ritardare la chiamata a SendAsync() fino a quando non so di averne bisogno.
Quindi, ho impostato il TCS e quindi ho avviato un'attività per eseguire l'autorizzazione. Nel seguito di questo, guardo il risultato. Se autorizzato, richiamo la catena di gestori interni e allego questo continuum a (evitando qualsiasi blocco di thread) che completa il TCS. Se l'autenticazione fallisce, compio il TCS lì e quindi con un 401.
Il risultato è che entrambe le attività asincrone vengono eseguite a turno senza alcun blocco del thread. Carico testato questo e sembra funzionare bene.
È tutto molto più bello in .NET 4.5 con la sintassi asincrona/attesa però ... anche se l'approccio con TCS sta ancora praticamente succedendo sotto le copertine, il codice è molto più semplice.
Divertiti!
Il primo frammento di codice è stato creato su .NET 4.0 con Web API Beta - il secondo su .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;
}
Ecco un'API di uscita versione Candidate .NET 4.5/Web, che è molto più sexy con il nuovo async/await sintassi:
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);
}
come hai implementato 'Authorize'? Hai installato un nuovo 'HttpClient'? – JobaDiniz
L'implementazione di Autorizza non è rilevante qui - è solo una funzione asincrona che determina se la richiesta è consentita - il modo in cui lo implementa dipende da te.Ad esempio, è possibile effettuare un controllo del database per verificare se l'utente corrente ha accesso per rendere la richiesta corrente basata su alcune regole aziendali personalizzate. –
Mi stavo chiedendo se hai fatto un'altra richiesta http, questo è tutto ... nel mio scenario ho bisogno di fare un'altra chiamata http, prima di quella corrente, e sono istanciata un altro 'HttpClient'. Mi stavo chiedendo se potevo riutilizzare quello attuale all'interno del gestore, ma sembra che non sia possibile – JobaDiniz