File upload with progress bar in Blazor
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!