8

Il problema oggi è che quando si utilizza WebApi 2 e un metodo Get async ApiController basato su, restituisce il contenuto di un file. Quando cambio il metodo Get in sincrono, funziona bene, ma non appena lo converto in async, chiude il flusso prematuramente. (Fiddler riporta la connessione è stata interrotta) Il codice sincrono di lavoro è:C# Async ApiController Chiusura OutputStream anticipatamente

public void Get(int id) 
    { 
     try 
     { 
      FileInfo fileInfo = logic.GetFileInfoSync(id); 
      HttpResponse response = HttpContext.Current.Response; 
      response.Clear(); 
      response.ClearContent(); 
      response.Buffer = true; 
      response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileInfo.Node.Name + fileInfo.Ext + "\""); 
      response.AddHeader("Content-Length", fileInfo.SizeInBytes.ToString()); 
      response.ContentType = "application/octet-stream"; 
      logic.GetDownloadStreamSync(id, response.OutputStream); 
      response.StatusCode = (int)HttpStatusCode.OK; 
      //HttpContext.Current.ApplicationInstance.CompleteRequest(); 
      response.End(); 
     } 
     catch(Exception ex) 
     { 
      Console.WriteLine(ex.ToString()); 
     } 
    } 

E la GetDownloadStreamSync è la seguente:

public async Task GetDownloadStream(string fileIdentifier, Stream streamToCopyTo) 
{ 
    string filePath = Path.Combine(fileIdentifierFolder, fileIdentifier); 
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, false)) 
    { 
     fs.CopyTo(streamToCopyTo); 
    } 
} 

-------- codice asincrono ----- -----

versione

Async è esattamente lo stesso, tranne:

public async Task Get(int id) 
{ 
    FileInfo fileInfo = await logic.GetFileInfoSync(id); // database opp 
      HttpResponse response = HttpContext.Current.Response; 
      response.Clear(); 
      response.ClearContent(); 
      response.Buffer = true; 
      response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileInfo.Node.Name + fileInfo.Ext + "\""); 
      response.AddHeader("Content-Length", fileInfo.SizeInBytes.ToString()); 
      response.ContentType = "application/octet-stream"; 
      await logic.GetDownloadStreamSync(id, response.OutputStream); 
          //database opp + file I/O 
      response.StatusCode = (int)HttpStatusCode.OK; 
      //HttpContext.Current.ApplicationInstance.CompleteRequest(); 
      response.End(); 
} 

Con l'implementazione asincrona di GetDo wnloadStream come segue: (streamToCopyTo è l'OutputStream dalla Response.OutputStream)

public async Task GetDownloadStream(string fileIdentifier, Stream streamToCopyTo) 
{ 
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, true)) 
    { 
     await fs.CopyToAsync(streamToCopyTo); 
    } 
} 

Stiamo cercando di abbracciare la async/await modello da davanti a dietro, quindi si spera che qualcuno è a conoscenza del motivo per cui questo sarebbe fallendo? Ho anche provato a non chiamare Response.End(), Response.Flush() e HttpContext.Current.ApplicationInstance.CompleteRequest(). Inoltre, in risposta alle domande/commenti di seguito, ho inserito un punto di interruzione sulla risposta.End() con il risultato che non è stato colpito al completamento del metodo GetDownloadStream. Forse OutputStream non è asincrono? Tutte le idee sono ben accette! Grazie

************************** Soluzione finale **************** ***********

Grande grazie a tutti coloro che hanno commentato, e in particolare a @Noseratio per il suo suggerimento su FileOptions.DeleteOnClose.

[HttpGet] 
public async Task<HttpResponseMessage> Get(long id) 
{ 
     HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
     Node node = await logic.GetFileInfoForNodeAsync(id); 

     result.Content = new StreamContent(await logic.GetDownloadStreamAsync(id)); 
     result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
     result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") 
     { 
      FileName = node.Name + node.FileInfo.Extension 
     }; 
     result.Content.Headers.ContentLength = node.FileInfo.SizeInBytes; 
     return result 
} 

Con il GetDownloadStreamAsync simile a questo:

FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, FileOptions.DeleteOnClose | FileOptions.Asynchronous); 

ho lasciato che mi è stato anche la decodifica del flusso di file al volo, e questo funziona, quindi per coloro che sono interessati ...

FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, FileOptions.DeleteOnClose | FileOptions.Asynchronous); 
RijndaelManaged rm = new RijndaelManaged(); 
return new CryptoStream(fs, GetDecryptor(rm, password), CryptoStreamMode.Read); 
+0

Ecco cosa la spiegazione di MSDN è: ". Un attendono espressione non blocca il filo su cui è in esecuzione, invece, fa sì che il compilatore per registrare il resto del metodo asincrono come una continuazione sull'attività attesa.Il controllo quindi ritorna al chiamante del metodo asincrono.Quando l'attività viene completata, richiama la sua continuazione, e l'esecuzione del metodo asincrono riprende da dove era stata interrotta Un'espressione di attesa può verificarsi solo nel corpo di un metodo che racchiude immediatamente, espressione lambda o metodo anonimo contrassegnato da un modificatore asincrono.Altrove, viene interpretato come un identificatore. " – user1789573

+0

Qual è la firma del metodo in async Astuccio? (Suppongo che 'Task Get ...', ma è meglio accertarsi). –

+0

Nel caso in cui manchi qualcosa nella tua domanda, puoi pubblicare la versione completa asincrona? – EZI

risposta

1

Sarebbe prendere per avere un caso Repro completa per rispondere alla tua domanda esatta, ma non credo che avete bisogno di async/await qui a tutti . Penso inoltre che dovresti evitare di usare HttpContext.Current.Response direttamente dove possibile, specialmente nei metodi di controllo WebAPI asincroni.

In questo caso particolare, è possibile utilizzare HttpResponseMessage:

[HttpGet] 
public HttpResponseMessage Get(int id) 
{ 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    FileInfo fileInfo = logic.GetFileInfoSync(id); 

    FileStream fs = new FileStream(
     filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, false); 

    result.Content = new StreamContent(fs); 
    result.Content.Headers.ContentType = 
     new MediaTypeHeaderValue("application/octet-stream"); 
    result.Content.Headers.ContentDisposition = 
     new ContentDispositionHeaderValue("attachment") 
     { 
      FileName = fileInfo.Node.Name + fileInfo.Ext 
     }; 
    result.Content.Headers.ContentLength = fileInfo.SizeInBytes; 

    return result; 
} 

Non c'è asincronia esplicita qui, in modo che il metodo non è async. Se invece hai ancora bisogno di introdurre un po 'await, il metodo vorrebbe in questo modo:

[HttpGet] 
public async Task<HttpResponseMessage> Get(int id) 
{ 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    // ... 
    await fs.CopyToAsync(streamToCopyTo) 
    // ... 
    return result; 
} 
+0

Domanda sulla prima soluzione, lascia aperto il FileStream? La mia soluzione di cui sopra ha una buona istruzione sull'uso per assicurarsi che sia chiusa e smaltita correttamente, mentre nella prima soluzione sono un po 'preoccupato che FileStream venga lasciato aperto, è vero? Sto cercando di avvolgere il FileStream in un utilizzo ed eliminare il file dall'unità locale una volta che il file è stato scaricato se questo ha più senso? – TChadwick

+1

@TChadwick, no non verrà lasciato aperto, il runtime WebAPI chiamerà 'Chiudi' sullo stream una volta terminato. Se hai bisogno di cancellarlo a quel punto, usa semplicemente 'FileOptions.DeleteOnClose | FileOptions.Asynchronous' come ultimo parametro per 'new FileStream'. – Noseratio

+0

@TChadwick, quindi hai questo approccio funzionante, alla fine? – Noseratio

1

La radice del problema si trova in realtà nell'uso di Response.End(). Quando si esegue Async, verrà eseguito il Response.End() prima di eseguire lo streaming del contenuto del file. Questo non si vede quando si utilizza la versione di sincronizzazione perché Response.End() non viene chiamato fino a quando non viene eseguito lo streaming del contenuto del file.

Response.End() è un modo ECCEZIONALMENTE pessimo per dire che hai finito di elaborare mentre lancia una TreadAbortException. Invece, si dovrebbe utilizzare HttpContext.Current.ApplicationInstance.CompleteRequest()

Vedere questo articolo per ulteriori informazioni Response.End, Response.Close, and How Customer Feedback Helps Us Improve MSDN Documentation

+0

Grazie per la tua risposta Matthew. Con o senza Response.End(), il problema è lo stesso, ho anche provato la CompleteRequest(). – TChadwick

+0

@TChadwick, questo è un helper dei contenuti che ho scritto di recente per aiutare i file di streaming al client attraverso la risposta HTTP. http://pastebin.com/5B29Pgdp Non è asincrono, ma dovrebbe funzionare in un contesto asincrono. Provalo e vedi se funziona. –

+0

Grazie ancora per le vostre risposte, ho approfondito questo aspetto e quando eseguo queste stesse chiamate in Sync, funzionano perfettamente. Ho scoperto che la chiamata a CompleteRequest() ha effettivamente modificato il codice di risposta in 204 (Nessun contenuto) invece di 200, impedendo al browser di scaricare il file e che dovevo chiamare Response.End() per ottenere un codice di 200 e farlo funzionare Voglio abbracciare la lettura del file asincrono e scriverlo su OutputStream in asincrono, ma a questo punto non sembra realistico ... – TChadwick