File upload with progress bar in Blazor

  • Gérald Barré

Uploading files may take times. The users want to have visibility on what's happening, so it is good to show them about the progress. The simplest way to do it is to show a progress bar. In this post, we'll use the InputFile component to upload files and some custom code to show the progress bar. The result looks like this:

Blazor comes with the InputFile component. This component allows you to upload files. In the code, you can handle the OnChange event to get the files selected by the user using GetMultipleFiles and access their content using OpenReadStream. The idea is to read the stream chunk by chunk and update the progress bar. For the UI, you can simply use the <progress> element.

When you update the UI, it triggers a re-rendering of the component. To not be too chatty, we will use a Timer to update the progress bar at a regular interval.

Here's the code:

@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. Also, it would be nice to show errors if the user tries to upload a file that is too big for instance. But I'll let these improvements to the readers.

#Additional resources

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

Follow me:
Enjoy this blog?Buy Me A Coffee