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