Reputation: 11
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
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();
});
});
Upvotes: 0
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