2012-06-14 7 views
11

Sono a mio agio con l'esecuzione del lavoro sincrono prima di chiamare SendAsync() del gestore interno e l'esecuzione del lavoro sincrono dopo il completamento del gestore interno. ad esempio:Come dovrebbe un DelegatingHandler effettuare una chiamata asincrona (API Web MVC ASP.NET)?

protected override Task<HttpResponseMessage> SendAsync( 
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    // do some sync work before inner handler here 

    var sendTask = base.SendAsync(request, cancellationToken); 
    return sendTask.ContinueWith( 
     task => { // do some sync work afterwards here }); 
} 

Tuttavia, ora è necessario chiamare un'operazione legata all'IO da un gestore delegante. L'operazione legata all'IO viene già completata come Task<bool>. Devo usare il risultato per determinare se continuare con il gestore interno.

Un esempio potrebbe effettuare una chiamata di rete per autorizzare una richiesta. Devo fare questo per integrarmi con un sistema esistente. In generale, penso che ci siano scenari validi per questo problema e che dovrebbe avere una soluzione praticabile.

Qual è il modo corretto di implementare SendAsync in questo caso, in modo da eseguire il task associato all'IO in modo asincrono e quindi continuare a eseguire in modo asincrono il gestore interno?

Il punto chiave è che voglio essere certo che il thread di richiesta non sia stato bloccato in nessun punto.

risposta

15

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); 
} 
+0

come hai implementato 'Authorize'? Hai installato un nuovo 'HttpClient'? – JobaDiniz

+0

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. –

+0

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

Problemi correlati