Fast loading of big files from a Blazor WASM application
Uploading relatively normal-sized files from Blazor is relatively straightforward. We use the InputFile
component, and a short routine. However, if we want to upload considerably large files, we encounter a technical challenge. I have seen several strategies, but not always efficient, for example, Carl Franklin's (Blazor Train) works, but it is slow. As described in this issue: github.com/dotnet/aspnetcore/issues/30243.
I have written a procedure that does not use serialization, and it is simple. It's really efficient, a ~200MB file easily loads in ~10 seconds.
Front-End
@page "/upload-big-file"
@inject HttpClient _httpClient
@using System.Net.Http.Headers
@using System.IO
<h1>Upload Big File</h1>
<hr />
<InputFile OnChange="@OnInputFileChange" disabled="@_uploading" />
<hr />
<div>@echo</div>
@code {// By: Harvey Triana
bool _uploading;
string echo;
async Task OnInputFileChange(InputFileChangeEventArgs e)
{
const long CHUNKSIZE = 1024 * 400; // subjective
var file = e.File;
long uploadedBytes = 0;
long totalBytes = file.Size;
long percent = 0;
int fragment = 0;
long chunkSize;
using (var inStream = file.OpenReadStream(long.MaxValue))
{
_uploading = true;
while (_uploading)
{
chunkSize = CHUNKSIZE;
if (uploadedBytes + CHUNKSIZE > totalBytes)
{// remainder
chunkSize = totalBytes - uploadedBytes;
}
var chunk = new byte[chunkSize];
await inStream.ReadAsync(chunk, 0, chunk.Length);
// upload this fragment
using var formFile = new MultipartFormDataContent();
var fileContent = new StreamContent(new MemoryStream(chunk));
formFile.Add(fileContent, "file", file.Name);
// post
var response = await _httpClient.PostAsync($"api/Files/AppendFile/{fragment++}", formFile);
// Update our progress data and UI
uploadedBytes += chunkSize;
percent = uploadedBytes * 100 / totalBytes;
echo = $"Uploaded {percent}% {uploadedBytes} of {totalBytes} | Fragment: {fragment}";
if (percent >= 100)
{// upload complete
_uploading = false;
}
await InvokeAsync(StateHasChanged);
}
}
}
}
BackEnd
A Controller from in an ASP.NET Web Api application
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using System;
using System.IO;
using IO = System.IO;
namespace BigBlob.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FilesController : ControllerBase
{
[HttpPost("AppendFile/{fragment}")]
public async Task<bool> UploadFileChunk(int fragment, IFormFile file)
{
try {
// ** let the hosted path
var fileName = @"C:\TEMP\" + file.FileName;
if (fragment == 0 && IO.File.Exists(fileName)) {
IO.File.Delete(fileName);
}
using (var fileStream = new FileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.None))
using (var bw = new BinaryWriter(fileStream)) {
await file.CopyToAsync(fileStream);
}
return true;
}
catch (Exception exception) {
Console.WriteLine("Exception: {0}", exception.Message);
}
return false;
}
}
}
NOTE. The hardcode directory C:\TEMP\
is for the example, and it works of course. In actual practice we inject IWebHostEnvironmen
or use another strategy for the route.
By: Luis Harvey Triana Vega | blazorspread.net