Learn ASP.NET Core 3.0 : MVC, Razor Pages, Web API, Entity Framework Core, and Blazor.
Microsoft's official documentation can be found here. Looking for professional online training courses? Next weekend batches are starting in November 2019. More details here.

Upload Files Using jQuery Ajax and JavaScript Interop in Blazor

Uploading files from client machine to the server is one of the fundamental operations in web applications. In ASP.NET Core MVC applications you can accomplish this task easily using HTML5 file input field and server side logic. However, Blazor doesn't have any built-in facility (so far) for uploading files. You either need to use some third-party solution or device your own technique to accomplish the task. If you want to use third-party feature rich solutions then take a look at Syncfusion's implementation and Steve Sanderson's file upload component.

In this article I am going to illustrate a traditional Ajax based approach to serve the purpose. In addition to uploading files to the server you will learn how JavaScript interop of Blazor works. What we will do is:

  • We display a file input field to the user that allows one or more files to be selected.
  • In addition to files you might have additional data entry fields.
  • Upon submitting the Blazor form, Blazor code invokes a client side JavaScript function using JS interop.
  • The JS function makes jQuery Ajax call to an API controller and sends the selected files to the server.
  •  API controller saves the files on the server.
  • Blazor is notified of the success or failure of the Ajax call.

The Razor Component that houses the required UI is shown below:

Let's get going.

Begin by creating Blazor server side app using Visual Studio IDE.

Add Employee model class

The data entry form that you create uses a model class called Employee. The Employee class consists of two properties EmployeeID and FullName. To add this class, right click on the Data folder and add a new C# class named Employee. Then modify the class as shown below:

public class Employee
{
    [Required]
    public string EmployeeID { get; set; }

    [Required]
    public string FullName{ get; set; }

}

Create Razor Component with data entry form

Now, right click on the Pages folder and add a new Razor Component called FileUpload.razor.

Then setup the route for the newly added component by adding this code at the top of the component file.

@page "/fileupload"
@using JQueryFileUploadBlazorServerSide.Data
@inject IJSRuntime JsRuntime;

The first line makes the Razor Component available as if it is a "page". This is done by associating /fileupload route with the component. The @using directive uses Data namespace. You need this namespace because you want to use the Employee model class created earlier. Finally, you also inject JsRuntime object into the component using @inject directive. This class is required to use Blazor's JavaScript interop.

Then add a data entry form to the component by writing the following code and markup.

<h1>Upload Files</h1>

<EditForm Model="emp" OnValidSubmit="OnSubmit">
    <DataAnnotationsValidator></DataAnnotationsValidator>
    <ValidationSummary></ValidationSummary>

    <table cellpadding="11">
        <tr>
            <td>Employee ID :</td>
            <td>
                <InputText id="employeeID" 
@bind-Value="emp.EmployeeID" />
                <ValidationMessage 
For="@(() => emp.EmployeeID)" />
            </td>
        </tr>
        <tr>
            <td>Full Name :</td>
            <td>
                <InputText id="fullName" 
@bind-Value="emp.FullName" />
                <ValidationMessage 
For="@(() => emp.FullName)" />
            </td>
        </tr>
        <tr>
            <td>Select File(s) To Upload :</td>
            <td>
                <input type="file" 
                       id="files" 
                       name="files" multiple />
            </td>
        </tr>
        <tr>
            <td colspan="2" align="left">
                <button type="submit">Upload Files</button>
            </td>
        </tr>
    </table>
</EditForm>
<br />
<h3>@Message</h3>

The <EditForm> component defines Blazor's data entry form. Note that <EditForm> specifies its Model to be emp object. This object is of type Employee and is defined later in the code. We require model validations for our form. So, <DataAnnotationsValidator>, <DataAnnotationsValidator>, and <ValidationMessage> components are used in the markup. To specify EmployeeID and FullName values you use <InputText> input component. These input components are validated using <ValidationMessage> component.

Notice the markup shown in bold letters. It's a file input field that allows selecting multiple files for uploading. At the bottom of the form there is a submit button that submits the form. The <EditForm> component's OnValidSubmit attribute is set to OnSubmit() handler method (discussed later). If all the input components contain valid values only then OnSubmit() will be invoked. Otherwise error messages are displayed as shown below:

At the bottom of the data entry form you output the value of Message property (discussed later). This is basically a success or error message indicating the status of the file upload operation.

Add @code block and model object

Now that the data entry form is complete, add @code block as shown below:

@code {
    Employee emp = new Employee();
    public string Message { get; set; }
}

As you can see, the @code block declares an Employee object. Recollect that <EditForm> component's Model is set to emp object. The code also creates the Message property. This property holds a message that is rendered on the page so that user is ware of the status of the file upload operation.

Write OnSubmit() method

Next, add a method called OnSubmit() to handle the form submission. This method is shown below:

private async void OnSubmit()
{
    //do something with Employee properties
    Message = $"Uploading files for {emp.FullName}
({emp.EmployeeID})";
    var flag = await JsRuntime.InvokeAsync<bool>
("UploadFiles",DotNetObjectReference.Create(this));
    StateHasChanged();
}

As you can see, the OnSubmit() method sets the Message property so that the user is aware of the file upload operation. At this stage file upload has just began. So, the Message simply informs the user about it. Note that the code uses EmployeeID and FullName prperties of Employee object just to display the message. In a more realistic situation you will store these details into database or process them in a way specific to your application.

The next line of code is important. It calls the InvokeAsync() method of JsRuntime in an attempt to invoke a JavaScript function. The InvokeAsync() call specifies that we want to invoke a JavaScript method named UploadFiles. It also specifies that the UploadFiles method is going to return a Boolean value.

Once file upload operation is complete we want UploadFiles to notify back to Blazor that the operation is complete. That means we also want to invoke Blazor code from the UploadFiles JavaScript function. To facilitate this JS to Blazor communication we pass a reference to the current component instance to the JS code. This is done using DotNetObjectReference.Create() method.

Write [JSInvokable] method

In the preceding section it was mentioned that the UploadFiles JavaScript function needs to call Blazor code in order to notify that the file upload operation is complete. This JS callable code resides as a component method that is decorated with [JSInvokable] attribute. Have a look at the following code:

[JSInvokable]
public bool SetMessage(string msg)
{
    Message = msg;
    StateHasChanged();
    return true;
}

The SetMessage() method accepts a string message and returns a Boolean value. This string message will be passed by the JS code. Inside, the string message is assigned to the Message property so that it gets displayed on to the page.

Write UploadFiles() JavaScript function

Now let's write the UploadFiles() JavaScript function hinted at in the preceding section. This function makes use of jQuery Ajax to send files to the server. Therefore, add jQuery library in the wwwroot/Scripts folder. Also add a new JavaScript file named FileUpload.js

A <script> reference needs to be added to these files from _Host.cshtml file. Locate _Host.cshtml from Pages folder and add these lines in the head section.

<script src="~/Scripts/jquery.js"></script>
<script src="~/Scripts/FileUpload.js"></script>

Then open FileUpload.js and write the UploadFiles() function in it as shown below:

function UploadFiles(instance) {
    var fileUpload = $("#files").get(0);
    var files = fileUpload.files;
    var data = new FormData();
    for (var i = 0; i < files.length; i++) {
        data.append(files[i].name, files[i]);
    }
    $.ajax({
        type: "POST",
        url: "/api/FileUpload",
        contentType: false,
        processData: false,
        data: data,
        success: function (message) {
            instance.invokeMethodAsync("SetMessage",
message)
                .then((result) => {
                    console.log(result);
                });
        },
        error: function () {
            instance.invokeMethodAsync("SetMessage",
 "Error while uploading files!")
                .then((result) => {
                    console.log(result);
                });
        }
    });
    return true;
}

The code shown above grabs the file input field and its files collection. It then creates a new FormData object and one-by-one files are appended to it. The resultant FormData object is sent to the server by making an Ajax call. The $.ajax() call specifies that the FormData is POSTed to /api/FileUpload end-point. This is actually FileUploadController's UploadFiles() action and you will write it in the next section.

Notice that the UploadFiles() function receives Blazor component class instance as its parameter (recollect the DotNetObjectReference.Create() call earlier). The success and error handler callbacks use this instance object to call Blazor's SetMessage() method. This is done using invokeMethodAsync() method. The invokeMethodAsync() method accepts the Blazor method name to invoke and returns a JavaScript Promise.  Here, we use then() method of the Promise to print the Boolean return value from SetMessage() to the console.

Add UploadFiles() API action to save files on the server

Next, add Controllers folder to the project and add a new API controller named FileUploadController. Then write UploadFiles() action into the FileUploadController as shown below:

[HttpPost]
public IActionResult UploadFiles()
{
    long size = 0;
    var files = Request.Form.Files;
    foreach (var file in files)
    {
        var filename = ContentDispositionHeaderValue
                        .Parse(file.ContentDisposition)
                        .FileName
                        .Trim('"');
        filename = hostingEnv.WebRootPath + $@"\{filename}";
        size += file.Length;
        using (FileStream fs = 
System.IO.File.Create(filename))
        {
            file.CopyTo(fs);
            fs.Flush();
        }
    }
    string message = $"{files.Count} file(s) / 
{size} bytes uploaded successfully!";
    return Json(message);
}

If you ever wrote file upload code in ASP.NET Core MVC apps then this code should look familiar to you. The code iterates through the Request.Form.Files collection and saves the uploaded files one-by-one on the server. The files are saved under /wwwroot folder. The physical path of wwwroot folder is obtained using the WebRootPath property of IWebHostEnvironment object injected into the controller (IWebHostEnvironment injected into the constructor is not shown for the sake of brevity).

Once all the files are saved on the server a success message containing total number of files uploaded and their size in bytes is sent back to the caller.

Final step is to configure routing for the API controllers in the Configure() method of Startup class.

app.UseEndpoints(endpoints =>
{
     endpoints.MapControllers();
     endpoints.MapBlazorHub();
     endpoints.MapFallbackToPage("/_Host");
});

This completes the application. Run the application and try uploading one or more files. Check whether the files get saved under wwwroot folder.

That's it for now! Keep coding!!


"Unless you learn to channelize three main dimensions of primordial energy - will, knowledge, and action - to manifest your life goals you aren't utilizing it in its true yogic sense."
#AjapaYogaByBipinJoshi

Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get article updates : Facebook  Twitter  LinkedIn

Posted On : 30 September 2019


Tags : ASP.NET ASP.NET Core MVC C# JavaScript Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates