File upload with progress bar in Blazor

 
 
  • Gérald Barré

File uploads can take time, and users want visibility into the progress. The simplest way to provide that feedback is a progress bar. In this post, we'll use the InputFile component to upload files and some custom code to display the progress bar. The result looks like this:

Blazor includes the InputFile component for file uploads. You can handle the OnChange event to retrieve files selected by the user via GetMultipleFiles, then access their content using OpenReadStream. The approach is to read each stream chunk by chunk and update the progress bar as data is read. For the UI, you can use the built-in <progress> element.

Updating the UI triggers a re-render of the component. To avoid re-rendering too frequently, we use a Timer to refresh the progress bar at a regular interval.

Here's the code:

Razor
@page "/"
@using System.Globalization
@using Meziantou.Framework

<h1>File upload with progress</h1>

<InputFile OnChange="e => LoadFiles(e)" multiple></InputFile>

@foreach (var file in uploadedFiles)
{
    <div>
        @file.FileName
        <progress value="@file.UploadedBytes" max="@file.Size"></progress>
        @file.UploadedPercentage.ToString("F1")%
        (@FormatBytes(file.UploadedBytes) / @FormatBytes(file.Size))
    </div>
}

@code {
    List<FileUploadProgress> uploadedFiles = new();

    private async ValueTask LoadFiles(InputFileChangeEventArgs e)
    {
        var files = e.GetMultipleFiles(maximumFileCount: 100);

        var startIndex = uploadedFiles.Count;

        // Add all files to the UI
        foreach (var file in files)
        {
            var progress = new FileUploadProgress(file.Name, file.Size);
            uploadedFiles.Add(progress);
        }

        // We don't want to refresh the UI too frequently,
        // So, we use a timer to update the UI every few hundred milliseconds
        await using var timer = new Timer(_ => InvokeAsync(() => StateHasChanged()));
        timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(500));

        // Upload files
        byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(4096);
        try
        {
            foreach (var file in files)
            {
                using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
                while (await stream.ReadAsync(buffer) is int read && read > 0)
                {
                    uploadedFiles[startIndex].UploadedBytes += read;

                    // TODO Do something with the file chunk, such as save it
                    // to a database or a local file system
                    var readData = buffer.AsMemory().Slice(0, read);
                }

                startIndex++;
            }
        }
        finally
        {
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);

            // Update the UI with the final progress
            StateHasChanged();
        }
    }

    // Use the Meziantou.Framework.ByteSize NuGet package.
    // You could also use Humanizer
    string FormatBytes(long value)
        => ByteSize.FromByte(value).ToString("fi2", CultureInfo.CurrentCulture);

    record FileUploadProgress(string FileName, long Size)
    {
        public long UploadedBytes { get; set; }
        public double UploadedPercentage => (double)UploadedBytes / (double)Size * 100d;
    }
}

You can go further by creating a reusable component. It would also be useful to display errors when a user tries to upload a file that exceeds the size limit. I'll leave these improvements as an exercise for the reader.

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?