File Upload With Progress Using Blazor WebAssembly And ASP.NET Core API

Introduction

 
Blazor is one of the most modern .NET technologies that allows .NET developers to build awesome, modern, and high-performance single-page applications. In addition to that all the .NET developers love the power of the razor engine that allows combining HTML & C# to create efficient components and pieces of UI.
 
Any piece of software is about data and forms are a crucial part of any application. Blazor has a built-in component called EditForm that handles all the process of validating & submitting the data from the client, and also contains a set of default Input components that allows the user to enter a different type of data such as (Text, Numbers, Files, Date ... etc.). 
 
In this series of articles, we will learn how to develop and build custom input controls and customize the experience of the user by overriding some default behaviors and building some components from very scratch to be able to build awesome apps that actually reflects your imagination and capabilities rather than being restricted with that already available only.
 
The code of the following tutorial is available on GitHub.
 
For part 1 we are going to learn how to use FileInput in Blazor WebAssembly to use it to upload a file from the client to an ASP.NET Core API in the backend and store it in the root folder of the API, and also learn how to customize the upload experience to show the progression of the current status of the upload (percentage & amount of upload KBs), so the user can see how much left until the file is fully uploaded, this feature is very important for all the modern applications because it keeps the user engaged with the flow instead of just seeing loading progress and he/she doesn't know when it's going to be done especially if the file that is being uploaded is a big one so let's get started with this journey (The final result of this article will look like this): 
 
File Upload With Progress using Blazor WebAssembly & ASP.NET Core API
 
1st of all open up your Visual Studio 2019 and create a new Blazor WebAssembly project and after setting the project name make sure to check the ASP.NET Core Hosted option so this means that the project will be initialized with an ASP.NET Core API project that serves the Blazor app and in this case, we don't need two hosts to host our application and we have the API ready for us directly, I will call my project for this demo "ProgressableFileUploader"
 
 File Upload With Progress using Blazor WebAssembly & ASP.NET Core API
 
2nd now let's start by writing the code on the server that will upload the file and return its URL back to the client. To get started go to the server project and create a new API controller and give it the name "FilesController" and make sure to import the System.IO namespace and then write the following action that receives a file and store it within the Data folder within the wwwroot (The code is self-explanatory),
 
File Upload With Progress using Blazor WebAssembly & ASP.NET Core API
// Improt the following namespace  
using System.IO;  
  
[HttpPost]  
public async Task<IActionResult> Upload([FromForm]IFormFile file)  
{  
   // Check if thefile is there   
   if (file == null)  
      return BadRequest("File is required");  
  
   // Get the file name   
   var fileName = file.FileName;  
  
   // Get the extension   
   var extension = Path.GetExtension(fileName);  
  
   // Validate the extension based on your business needs   
  
   // Generate a new file to avoid dublicates = (FileName withoutExtension - GUId.extension)  
   var newFileName = $"{Path.GetFileNameWithoutExtension(fileName)}-{Guid.NewGuid().ToString()}{extension}";  
  
   // Create the full path of the file including the directory (For this demo we will save the file insidea folder called Data within wwwroot)  
   var directoryPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Data");  
   var fullPath = Path.Combine(directoryPath, newFileName);  
  
   // Maek sure the directory is ther bycreating it if it's not exist  
   Directory.CreateDirectory(directoryPath);  
  
   // Create a new file stream where you want to put your file and copy the content from the current file stream to the new one   
   using (var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write))  
   {  
      // Copy the content to the new stream   
      await file.CopyToAsync(fileStream);  
   }  
  
   // You are done return the new URL which is (yourapplication url/data/newfilename)  
   return Ok($"https://localhost:44302/Data/{newFileName}");  
}  

Now we can access that endpoint, through the following URL "POST: https://yourapplication/api/files"

As you have seen we used the attribute [FromForm] before the IFormFile parameter because the only way to send a file is through a Post/Put request is in a form content or we have to transform the file into string representation to be able to send it as a JSON or other formats which is not recommended at all, and for now, this is all that you need for the server and we can move to the client part
 
The third step is to move to the Blazor WebAssembly application and create a code-behind file for the Index.razor component in the Pages folder as follows (Make sure to call the file of the class Index.razor.cs because this will make Visual Studio group the class within the component files together in one node).
 
File Upload With Progress using Blazor WebAssembly & ASP.NET Core API
 
Then we should mark this class as partial because it's part of the Index class and the other part is the Index component and also inherits from ComponentBase  so we have the class that looks like this,
using Microsoft.AspNetCore.Components;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  
  
namespace UploadFileWithProgress.Client.Pages  
{  
    public partial class Index
    {  
  
    }  
}  

Now let's move to the Index.razor component and create a simple input form with a button to send that file to the server as follows.

The following code has a sample UI for an InputFile, label, and an upload button, InputFile is the new input component by Microsoft to handle the process of choosing a file to be uploaded,

@page "/"  
  
<h1>Welcome to C# Corner</h1>  
<p>Learn how to upload a file from Blazor WebAssembly to the an ASP.NET Core Web API with progress</p>  
  
<div class="row">  
    <div class="col-4">  
        <div class="form-group">  
            <label>File</label>  
            <InputFile OnChange="OnChooseFile" />  
            <p>0KB / 9,500KB</p>  
        </div>  
        <button class="btn btn-success btn-block m-1">Upload</button>   
    </div>  
</div>  

 Also in the code-behind file create the following method "OnChooseFile" which is the method that will handle choosing the file by the InputFile component and make sure to import the namespace "Microsoft.AspNetCore.Components.Forms" like the following code,

using Microsoft.AspNetCore.Components;  
using Microsoft.AspNetCore.Components.Forms;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  
  
namespace UploadFileWithProgress.Client.Pages  
{  
    public partial class Index
    {  
  
        public void OnChooseFile(InputFileChangeEventArgs e)  
        {  
  
        }  
  
    }  
}  

Now if we run the project we should see something like this,

File Upload With Progress using Blazor WebAssembly & ASP.NET Core API
 
So the upcoming part is to implement the OnChooseFile function that will let the user choose a file and prepare to click upload, the following code is self-explanatory,
// MAke sure to import the namespace
using System.IO; 

// Create a global variable that will be used by OnChooseFile and UploadAsync methods   
       private Stream _fileStream = null;  
       private string _selectedFileName = null;   
  
       public void OnChooseFile(InputFileChangeEventArgs e)  
       {  
           // Get the selected file   
           var file = e.File;  
  
           // Check if the file is null then return from the method   
           if (file == null)  
               return;  
  
           // Validate the extension if requried (Client-Side)  
  
           // Set the value of the stream by calling OpenReadStream and pass the maximum number of bytes allowed because by default it only allows 512KB  
           // I used the value 5000000 which is about 50MB  
           using (var stream = file.OpenReadStream(50000000))  
           {  
               _fileStream = stream;  
               _selectedFileName = file.Name;   
           }  
       }  

So after we created that function we are able now to move to the other part which is uploading but before we doing so let's explain a little bit how the uploading process happens so we know how we can make it progressive.

Uploading a file to an API happens through submitting a POST/PUT request to the API with a body of Mutlipart-Form data which means the body is a form with a set of key-values and those key-values could be as much as you want and they could also be in two types (StringContent like ("Id", "123")) or (StreamContent ("file", stream of the file)) and after submitting the StreamContent of the file to the API the requests goes as chunks of bytes the default StreamContent class in .NET doesn't support the measurement of the number of bytes that have been submitted to the server so what we have to do is to create our own custom StreamContent class that holds an event called OnProgress so we can subscribe for that event and it gives as arguments the total amount of bytes to complete and the value of upload bytes after each uploaded chunk that we can specify its value, so let's move to the next step and create our own StreamContent class and call it ProgressiveStreamContent as follows (Code is self-explaintory).
 
You can create a file in the project and call it ProgressiveStreamContent.cs,
public class ProgressiveStreamContent : StreamContent
    {
        // Define the variables which is the stream that represents the file
        private readonly Stream _fileStream;
        // Maximum amount of bytes to send per packet
        private readonly int _maxBuffer = 1024 * 4; 

        public ProgressiveStreamContent(Stream stream, int maxBuffer, Action<long, double> onProgress) : base(stream)
        {
            _fileStream = stream;
            _maxBuffer = maxBuffer;
            OnProgress += onProgress;
        }

        /// <summary>
        /// Event that we can subscribe to which will be triggered everytime after part of the file gets uploaded.
        /// It passes the total amount of uploaded bytes and the percentage as well
        /// </summary>
        public event Action<long, double> OnProgress;

        // Override the SerialzeToStreamAsync method which provides us with the stream that we can write our chunks into it
        protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            // Define an array of bytes with the the length of the maximum amount of bytes to be pushed per time
            var buffer = new byte[_maxBuffer];
            var totalLength = _fileStream.Length;
            // Variable that holds the amount of uploaded bytes
            long uploaded = 0;

            // Create an while loop that we will break it internally when all bytes uploaded to the server
            while (true)
            {
                using (_fileStream)
                {
                    // In this part of code here in every loop we read a chunk of bytes and write them to the stream of the HttpContent 
                    var length = await _fileStream.ReadAsync(buffer, 0, _maxBuffer);
                    // Check if the amount of bytes read recently, if there is no bytes read break the loop
                    if (length <= 0)
                    {
                        break;
                    }

                    // Add the amount of read bytes to uploaded variable
                    uploaded += length;
                    // Calculate the percntage of the uploaded bytes out of the total remaining 
                    var perentage = Convert.ToDouble(uploaded * 100 / _fileStream.Length);

                    // Write the bytes to the HttpContent stream
                    await stream.WriteAsync(buffer);

                    // Fire the event of OnProgress to notify the client about progress so far
                    OnProgress?.Invoke(uploaded, percentage);

                    // Add this delay over here just to simulate to notice the progress, because locally it's going to be so fast that you can barely notice it
                    await Task.Delay(250);
                }
            }
        }

    }

Actually, this is all you need to do, the last step is just to submit or file to the server and use our new class and show the progress in the UI so let's go ahead and do that.

Now back to the Index.razor.cs the code-behind file.

Let's create two local fields to be used in the UI to show the percentage and the number of uploaded bytes and then write the method that will upload the file to the server (Code is self-explanatory).

 private long _uploaded = 0;
        private double _percentage = 0;
        // The method that will submit the file to the server
        public async Task SubmitFileAsync()
        {
            // Create a mutlipart form data content which will hold the key value of the file that must be of type StreamContent
            var content = new MultipartFormDataContent();

            // Create an instance of ProgressiveStreamContent that we just created and we will pass the stream of the file for it
            // and the 40096 which are 40KB per packet and the third argument which as a callback for the OnProgress event (u, p) are u = Uploaded bytes and P is the percentage
            var streamContent = new ProgressiveStreamContent(_fileStream, 40096, (u, p) =>
            {
                // Set the values of the _uploaded & _percentage fields to the value provided from the event
                _uploaded = u;
                _percentage = p;

                // Call StateHasChanged() to notify the component about this change to re-render the UI
                StateHasChanged();
            });

            // Add the streamContent with the name to the FormContent variable
            content.Add(streamContent, "File");

            // Submit the request 
            var response = await Client.PostAsync("/weatherforecast", streamContent);
        }

Now in the index.razor make the changes to call the Upload method and set the values of the percentage & uploaded/total KB as following,

<div class="row">  
    <div class="col-4">  
        <div class="form-group">  
            <label>File</label>  
            <InputFile OnChange="OnChooseFile" />  
            @* Show the value of the _uploaded variable divided to 1024 to show the amount in KB and also we use _fileStream?.Length / 1024 to show the total amount of KBs  *@
            <p>@(_uploaded / 1024)KB / @(_fileStream?.Length / 1024)KB</p>  
            @* Show the percentage of uploaded amount of of the total *@
            <p>Percentage: @_percentage %</p>
        </div>  
        @* Call the SubmitFileAsync in the @onclick event *@
        <button type="submit" class="btn btn-success btn-block m-1" @onclick="SubmitFileAsync">Upload</button>   
    </div>  
</div>  

Now we are ready to go' let's run the project and choose a file like about 40MB and see the progress in real-time, you should see something like this:

File Upload With Progress using Blazor WebAssembly & ASP.NET Core API

I hope you enjoyed this tutorial and you are able to apply the concept in your application in your style and way

The code is available on GitHub at this link.


Similar Articles