Bartosz
Bartosz

Reputation: 4766

FileStreamResult does not indicate download start

I have a following action on my MVC Controller:

  public async Task<FileStreamResult> Contact()
        {
            IAmazonS3 s3Client = GetS3Client();
            GetObjectRequest request = new GetObjectRequest
            {
                BucketName = bucketName,
                Key = objectKey,
            };
            GetObjectResponse response = await s3Client.GetObjectAsync(request).ConfigureAwait(false);
            return File(response.ResponseStream, "application/octet-stream", "SomeFile.exe");
        }

The files I am testing are ~10-30MB.

When this action is triggered by a button on the page, the download start is not indicated. The browser silently waits for the file to be fully available (which takes quite a lot of time), and then it suddenly shows them as downloaded.

Why doesn't it show that the download is started and is in progress? How to enable that?

The files are quite large, and I want them to be sent/streamed to the client without being loaded to memory or pre-saved on my server, so I suppose this is what the FileStreamResult is for?

Upvotes: 2

Views: 1942

Answers (1)

Bartosz
Bartosz

Reputation: 4766

Well, the simple answer seems to be:
The download start & progress are not visible in browser, because the content length is not set, so the browser cannot figure them out from the FileStreamResult.

So, it should be enough to set it. For me it worked best by extending the ActionResult with something which allows setting the length:

public class FileStreamWithLengthResult : ActionResult
{
    private Stream stream;
    private string mimeType;
    private string fileName;
    private long contentLength;

    public FileStreamWithLengthResult(Stream stream,string mimeType,string fileName)
    {
        this.stream = stream;
        this.mimeType = mimeType;
        this.fileName = fileName;
        this.contentLength = stream.Length;
    }


    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.BufferOutput = false;
        response.Headers.Add("Content-Type", mimeType);
        response.Headers.Add("Content-Length", contentLength.ToString());
        response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);

        stream.CopyTo(response.OutputStream);
    }
}

Then the Action code was as simple as

  public async Task<FileStreamWithLengthResult> Download()
        {
            IAmazonS3 s3Client = GetS3Client();
            GetObjectRequest request = new GetObjectRequest
            {
                BucketName = bucketName,
                Key = objectKey,
            };
            GetObjectResponse response = await s3Client.GetObjectAsync(request).ConfigureAwait(false);
            return new FileStreamWithLengthResult(response.ResponseStream, "application/octet-stream", "SomeFile.exe");

        }

As per this answer.

Also, what did not work for me was setting the "Content-Lenght" header like that:

this.ControllerContext.HttpContext.Response.AddHeader("Content-Length", response.ResponseStream.Length.ToString()); //not worked
this.Response.AddHeader("Content-Length", response.ResponseStream.Length.ToString()); //not worked
this.HttpContext.Response.AddHeader("Content-Length", response.ResponseStream.Length.ToString()); //not worked

I suppose these all point to the same object instance, not sure why it didn't work though. Anyway, custom FileStreamWithLengthResult works OK and looks more elegant:)

Upvotes: 1

Related Questions