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