C # Async ApiController Closing OutputStream Prematurely

The problem today is that when using the WebApi 2 method and the Get method based on Async ApiController, which returns the contents of the file. When I change the Get method to synchronous, it works fine, but as soon as I reinstall it back into async, it closes the stream prematurely. (Fiddler reports that the connection was interrupted). Working synchronous code:

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

And GetDownloadStreamSync looks like this:

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

-------- Asynchronous code ----------

Async version is exactly the same except:

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

With an asynchronous implementation of GetDownloadStream as follows: (streamToCopyTo is the OutputStream from 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); } } 

We are trying to cover an asynchronous / waiting template from front to back, so hopefully someone knows why this will fail. I also tried not to call Response.End (), Response.Flush () and HttpContext.Current.ApplicationInstance.CompleteRequest (). In addition, in response to the questions / comments below, I set a breakpoint on response.End () with a result that was not achieved by the GetDownloadStream method. Maybe OutputStream is not asynchronous? Any ideas are welcome! Thanks

*************************** Final decision ******************** *** *****

Many thanks to everyone who commented, and especially @Noseratio for his suggestion about 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 } 

With GetDownloadStreamAsync is as follows:

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

I forgot that I also decrypted the file stream on the fly, and it really works, so for those interested ...

 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); 
+8
c # asp.net-mvc asp.net-web-api async-await asp.net-web-api2
source share
2 answers

In order to answer your exact question, full reproduction would be required, but I don't think you need async/await here at all. I also think that you should avoid using HttpContext.Current.Response where possible, especially in the asynchronous methods of the WebAPI controller.

In this particular case, you can use 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; } 

There is no explicit asynchrony here, so the method is not async . If you still need to introduce a few await , this method would do so:

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

The root of your problem is actually using Response.End() . When you start Async, it will execute Response.End() before streaming the contents of the file. This is not visible when using the Sync version, because Response.End() not called until the file contents are streamed.

Response.End() - An EXCLUSIVELY bad way of saying that you have finished processing because it throws a TreadAbortException. Instead, you should use HttpContext.Current.ApplicationInstance.CompleteRequest()

See this article for more information. Response.End, Response.Close and How Customer Reviews Help Us Improve MSDN Documentation

+1
source share

All Articles