Attila Kiss
Attila Kiss

Reputation: 11

Blazor WASM download large (>2GB) file from WEB API

I'm stucked... I've read all of the threads about this and can't make it work and don't know why... I've a WEB API which should return a large zip file (>2GB), it creates the zip on disk, it's ok and returns with this:

return new FileStreamResult(new FileStream(Path.Combine(<path_to_file>), FileMode.Open), "application/zip") { FileDownloadName = generatedFileName };

On the client side I've a Blazor WASM and getting the result this way:

var request = new HttpRequestMessage(HttpMethod.Get, uri);                
request.Headers.Add("session", <session_data>);
... <other headers>
var result = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (result.IsSuccessStatusCode)
{      
    Console.WriteLine("Getting stream...");
    var responseStream = await result.Content.ReadAsStreamAsync();
    Console.WriteLine("Stream done");
}

When the zip is relatively small (4-500MB) it works well, but when I try to download for example a 3 GB zip, the "Stream done" line never reached, instead of the I'm getting an error in the output:

"TypeError: Failed to fetch at System.Net.Http.BrowserHttpInterop.d__13`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext() at System.Net.Http.BrowserHttpContent.GetResponseData(CancellationToken cancellationToken) at System.Net.Http.BrowserHttpContent.CreateContentReadStreamAsync() " and the line number where the ReadAsStreamAsync is.

What can be the problem here? Trying to solve it in the last two days but nothing helps... Thanks!

I've tried everything :)

Upvotes: 0

Views: 612

Answers (2)

Jalpa Panchal
Jalpa Panchal

Reputation: 12759

You could try this below code:

Blazor project:

Index.html:

<script>
    async function downloadFileFromUrl(url) {
        const response = await fetch(url, {
            headers: {
                'session': 'your_session_data' // Add other headers if needed
            }
        });
        if (!response.ok) {
            throw new Error(`Failed to download file: ${response.statusText}`);
        }
        const blob = await response.blob();
        const urlBlob = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = urlBlob;
        a.download = 'new.zip';
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(urlBlob);
    }
</script>

DownloadFile.razor:

@page "/download-file"
   @inject IJSRuntime JS

<h3>Download Large File</h3>

<button @onclick="DownloadFileAsync">Download File</button>

@code {
    private async Task DownloadFileAsync()
    {
        var uri = "https://localhost:7089/api/FileDownload/download-large-file";
        await JS.InvokeVoidAsync("downloadFileFromUrl", uri);
    }
   }

Program.cs:

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7089/") });

Webapi:

FileDownloadController.cs:

using Microsoft.AspNetCore.Mvc;

namespace WebApplication3.Controllers
{
    [ApiController]
    [Route("api/[controller]")]

    public class FileDownloadController : ControllerBase
    {
        [HttpGet("download-large-file")]
        public IActionResult DownloadLargeFile()
        {
            var filePath = Path.Combine("file", "test.zip");
            var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            return new FileStreamResult(fileStream, "application/zip")
            {
                FileDownloadName = "new.zip"
            };
        }

    }
}

Program.cs:

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAllOrigins",
        builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        });
});

enter image description here

Upvotes: 0

mrSoh
mrSoh

Reputation: 308

My guess is that you're running into a memory overflow. Maybe you can try streaming the file with JS Interop in chunks to prevent this.

Let's say your endpoint handler looks like this:

[HttpGet("download/{fileName}")]
public IActionResult DownloadFile(string fileName)
{
    var filePath = Path.Combine(<path_to_file>, fileName);
    var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 8192, useAsync: true);
    return new FileStreamResult(stream, "application/zip")
    {
        FileDownloadName = fileName
    };
}

Now on the client side app, since in Blazor WASM (as far as I know) directly saving a streamed response to a file is not supported/straightforward due to the lack of direct file system access. So we can use JS Interop for it.

For example:

//...
//rest of your code/ headers, etc...
//...
var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
    var fileName = "downloaded.zip"; // Change as needed
    await using var responseStream = await response.Content.ReadAsStreamAsync();
    await SaveFileFromStream(fileName, responseStream); //this is your saving method
}

For the SaveFileFromStream(fileName, responseStream) method:

private async Task SaveFileFromStream(string fileName, Stream stream)
{
    // Use JavaScript interop to save the file
    var jsStream = new DotNetStreamReference(stream);
    await JS.InvokeVoidAsync("saveAsFile", fileName, jsStream);
}

For the saveAsFile JS function, this would be an example:

//this goes in your custom js file under /wwwroot/{yourJSFile}.js
//you need to reference it in /wwwroot/index.html

window.saveAsFile = async (fileName, streamRef) => {
        const arrayBuffer = await streamRef.arrayBuffer();
        const blob = new Blob([arrayBuffer]);
        const url = URL.createObjectURL(blob);
        const anchorElement = document.createElement('a');
        anchorElement.href = url;
        anchorElement.download = fileName;
        anchorElement.click();
        URL.revokeObjectURL(url);
    };

Upvotes: 0

Related Questions