8

Das Problem bei der Verwendung von WebApi 2 und einer auf Async ApiController basierenden Get-Methode besteht darin, dass der Inhalt einer Datei zurückgegeben wird. Wenn ich die Get-Methode auf "synchron" ändere, funktioniert das problemlos, aber sobald ich es wieder in async konvertiere, schließt es den Stream vorzeitig. (Fiedler, berichtet die Verbindung abgebrochen wurde) Der Arbeits Synchron-Code ist:C# Async ApiController Vorzeitiges Schließen von OutputStream

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()); 
     } 
    } 

Und das GetDownloadStreamSync ist wie folgt:

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

-------- Async-Code ----- -----

die Async-Version ist genau das gleiche, außer:

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(); 
} 

Mit der Asynchron-Implementierung von GetDo wnloadStream wie folgt: (streamToCopyTo ist die Output vom 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); 
    } 
} 

Wir versuchen, die async/await Muster von vorne zu umarmen, zu sichern, so hoffentlich jemand ist sich bewusst, warum dies nicht möglich wäre? Ich habe auch versucht, Response.End(), Response.Flush() und HttpContext.Current.ApplicationInstance.CompleteRequest() nicht aufzurufen. Als Reaktion auf die folgenden Fragen/Kommentare habe ich auch einen Haltepunkt auf die Antwort platziert.End() mit dem Ergebnis, dass die GetDownloadStream-Methode nicht getroffen wurde. Vielleicht ist der OutputStream nicht asynchron? Irgendwelche Ideen sind willkommen! Dank

************************** Endlösung **************** ***********

Vielen Dank an alle, die kommentiert haben, und besonders an @Noseratio für seinen Vorschlag zu 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 
} 

Mit dem GetDownloadStreamAsync wie folgt aussehen:

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

verließ ich heraus, dass ich auch im Fluge den Dateistrom wurde zu entschlüsseln, und dies funktioniert, so für die Interessenten ...

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

Hier ist, was MSDN Erklärung ist. „Ein Ausdruck erwarten den Faden nicht blockieren, auf dem es ausgeführt wird Stattdessen bewirkt, dass es die Compiler, um den Rest der asynchronen Methode als Fortsetzung der erwarteten Aufgabe zu registrieren Control kehrt dann zum Aufrufer der asynchronen Methode zurück Wenn die Task abgeschlossen ist, ruft sie ihre Fortsetzung auf und die Ausführung der asynchronen Methode wird dort fortgesetzt, wo sie abgebrochen wurde Ein await-Ausdruck kann nur im Rumpf einer unmittelbar umschließenden Methode, eines Lambda-Ausdrucks oder einer anonymen Methode auftreten, die durch einen asynchronen Modifizierer markiert ist. Andernfalls wird sie als Bezeichner interpretiert. " – user1789573

+0

Was ist die Signatur der Methode in async? Fall? (Ich nehme 'Task Get ...' an, aber es ist besser sicher). –

+0

Falls Sie etwas in Ihrer Frage vermissen, können Sie die vollständige Async-Version veröffentlichen? – EZI

Antwort

1

Es nehmen würde einen vollständigen Repro Fall, um Ihre genaue Frage zu beantworten sein, aber ich glaube nicht, Sie async/await hier überhaupt brauchen . Ich denke auch, dass Sie vermeiden sollten, HttpContext.Current.Response direkt wo möglich zu verwenden, besonders in asynchronen WebAPI-Controller-Methoden.

In diesem speziellen Fall könnten Sie HttpResponseMessage verwenden:

[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; 
} 

Es gibt keine hier explizit Asynchronität, so ist das Verfahren nicht async. Wenn Sie jedoch noch einige await einführen müssen, würde die Methode wie folgt erhalten:

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

Frage über die erste Lösung, lässt es den FileStream geöffnet? Meine Lösung hat oben ein schöne drum herum mit Anweisung, um sicherzustellen, dass sie geschlossen ist und ordnungsgemäß entsorgt werden, während in der ersten Lösung bin ich etwas besorgt über das Öffnen von File gelassen wird, ist das wahr? Ich versuche, den FileStream in eine Verwendung zu wickeln und die Datei vom lokalen Laufwerk zu löschen, sobald die Datei heruntergeladen wurde, wenn das sinnvoller ist? – TChadwick

+1

@TChadwick, nein es wird nicht offen gelassen wird, wird die WebAPI Laufzeit 'Close' auf dem Strom rufen, sobald es mit ihm fertig ist. Wenn Sie es an diesem Punkt löschen müssen, verwenden Sie einfach 'FileOptions.DeleteOnClose | FileOptions.Asynchronous 'als letzter Parameter zu' new FileStream'. – Noseratio

+0

@TChadwick, hast du diesen Ansatz schließlich funktioniert? – Noseratio

1

Die Wurzel Ihres Problems liegt tatsächlich in der Verwendung von Response.End(). Wenn Sie Async ausführen, wird die Datei Response.End() ausgeführt, bevor der Inhalt der Datei gestreamt wird. Bei Verwendung der Sync-Version wird dies nicht angezeigt, da Response.End() erst aufgerufen wird, nachdem der Dateiinhalt gestreamt wurde.

Response.End() ist eine AUSSERGEWÖHNLICHE schlechte Möglichkeit zu sagen, dass Sie die Verarbeitung abgeschlossen haben, da es eine TreadAbortException auslöst. Stattdessen sollten Sie HttpContext.Current.ApplicationInstance.CompleteRequest()

Lesen Sie diesen Artikel für weitere Informationen Response.End, Response.Close, and How Customer Feedback Helps Us Improve MSDN Documentation

+0

Vielen Dank für Ihre Antwort Matthew. Mit oder ohne Response.End() ist das Problem das gleiche, ich habe auch die CompleteRequest() ausprobiert. – TChadwick

+0

@TChadwick, dies ist ein Inhaltshelfer, den ich kürzlich geschrieben habe, um Dateien über die HTTP-Antwort zum Client zu streamen. http://pastebin.com/5B29Pgdp Es ist nicht asynchron, aber es sollte in einem asynchronen Kontext funktionieren. Probieren Sie es aus und sehen Sie, ob es funktioniert. –

+0

Nochmals vielen Dank für Ihre Antworten, ich habe weiter in dies gegraben, und wenn ich diese gleichen Anrufe in Sync ausführen, funktionieren sie gut. Ich fand, dass CompleteRequest() aufrufen verändert tatsächlich die Antwortcode 204 (kein Inhalt) statt 200 verursacht der Browser die Datei nicht herunterladen, und ich hatte Response.End() aufrufen, um es einen 200-Code zu erhalten zurückzukehren und Lass es funktionieren. Ich möchte das Lesen der Datei async und das Schreiben in den OutputStream in async, aber es scheint zu diesem Zeitpunkt unrealistisch zu sein ... – TChadwick