4

I have the following action that is generating a file:

public async Task<IActionResult> GetFile()
{
    //some long running action to actually generate the content 
    //(e.g. getting data from DB for reporting)
    await Task.Delay(10000); 

    return File(new byte[]{}, "application/octet-stream");
}

The problem is that when user follows that link, there's a long delay between hitting an action and the browser displaying the file as being downloaded.

What I actually want, is for ASP.Net to send headers with filename (so the browser would show to the user that file has started to download) and delay sending the Body while file contents is generated. Is it possible to do this?

I tried the following:

public async Task GetFile()
{
    HttpResponse response = this.HttpContext.Response;
    response.ContentType = "application/octet-stream";
    response.Headers["Content-Disposition"] = "inline; filename=\"Report.txt\"";

    //without writing the first 8 bytes 
    //Chrome doesn't display the file as being downloaded 
    //(or ASP.Net doesn't actually send the headers). 
    //The problem is, I don't know the first 8 bytes of the file :)            
    await response.WriteAsync(string.Concat(Enumerable.Repeat("1", 8)));

    await response.Body.FlushAsync();

    await Task.Delay(10000);
    await response.WriteAsync("1234567890");
}

The code above works, and I'd be happy with it, if I didn't have to response.WriteAsync first 8 bytes for headers to be sent.

Are there other ways to overcome it?

kennyzx
  • 12,845
  • 6
  • 39
  • 83
Shaddix
  • 5,901
  • 8
  • 45
  • 86
  • 3
    I could have sworn that there was some type that let you return some type of `FileContentResult`, in which you effectively promised to provide access to a `Stream` like object from which it could pull the bytes as it was ready to send them over the wire, but I cannot for the life of my find it now. I guess if I just dreamt it you could bodge something together implementing some `Stream` like class yourself to encapsulate the idea of "I'll supply the bytes when the network's ready for them". – Damien_The_Unbeliever Oct 18 '18 at 07:13
  • `File` takes a 3rd parameter of `enablePartialContent` set that to true, see if it works. – Anton Toshik Oct 18 '18 at 07:59
  • There's FileStreamResult (https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/FileStreamResult.cs), but according to the sourcecode it reads the stream till the end and exits (i.e. if there's no data in the stream it will exit immediately, without waiting for stream to be populated with data) – Shaddix Oct 18 '18 at 08:07
  • 3rd parameter is currently renamed to enableRangeProcessing, which is more descriptive (it's about Accept-Range headers for partial/parallel downloads) – Shaddix Oct 18 '18 at 08:09
  • 2
    @Damien_The_Unbeliever I think you might be referring to [`PushStreamContent`](https://blog.stephencleary.com/2016/10/async-pushstreamcontent.html). – Kirk Larkin Oct 18 '18 at 10:37
  • 1
    Stephen Cleary wrote a post on this that might be helpful: [Streaming Zip on ASP.NET Core](https://blog.stephencleary.com/2016/11/streaming-zip-on-aspnet-core.html). Obviously, it's about writing a .zip but the concepts should apply for your use-case. – Kirk Larkin Oct 18 '18 at 10:38
  • It should've worked (https://gist.github.com/Shaddix/f22feaffcc99fe653d8b8455ee967e19), but I experience the same behavior as in my question (like I didn't write first 8 bytes to the response). Running Invoke-WebRequest in Powershell however shows that headers are actually sent. So it seems like a browser-related issue. Thanks for help! – Shaddix Oct 18 '18 at 12:41
  • `Content-Disposition` should be `attachment; filename=\"Report.txt\"`; not `inline; filename=\"Report.txt\"`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition. Inline means, it should be displayed within browser if applicable (such as a image, or pdf file) But @AntonToshik comment should be used since it should have the proper overload parameters to send it as downloadable file – Tseng Oct 18 '18 at 14:20
  • @kirk-larkin could you please post your comment as an answer so I could accept? – Shaddix Oct 20 '18 at 05:55

1 Answers1

1

Actually, the code in question works correctly. The problem is, that Chrome doesn't show file as being downloaded until 8 bytes are sent

However, if you'd want to write something similar, consider reading Stephen Cleary's post few useful abstractions.

Shaddix
  • 5,901
  • 8
  • 45
  • 86