Uploading Files In Blazor

A few weeks ago, Steve Sanderson blogged about publishing a package for a prototype Blazor file input component, designed to make working with user-supplied files in Blazor applications a lot easier. Steve has provided some very clear examples demonstrating how to use the component in a number of scenarios. Missing, though, is an example showing how to make an HTTP request to upload a file to some back-end server from a Blazor WebAssembly application.

Before moving forward, it is important to point out that WASM-based Blazor is still in preview as I write, and the BlazorInputFile package is no more than a prototype, which may in fact make its way into the Blazor framework at some stage but could undergo radical redesign before that happens, if it ever happens.

Disclaimers out of the way, if you want to play along, you need to create a Blazor WebAssembly App, with ASP.NET Core Hosted checked:

Blazor WASM

Install Steve's BlazorInput package into the client project. Then create a new JavaScript file and copy the contents of the original source on GitHub into it. Reference the JavaScript file you just created in the index.html file, which is in the wwwroot folder. In the example below, the file has been named inputfile.js and placed in a folder named js in wwwroot.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>BlazorWasmTests<title/>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>Loading...</app>

    <script src="_framework/blazor.webassembly.js"></script>
    <script src="js/inputfile.js"></script>
</body>
</html>

The requirement to copy the JavaScript from the source will not be necessary once WASM-based Blazor is released properly, which is expected to be some time around May 2020.

Next, add two using statements to the _Imports.razor file to make the component available to the project along with the System.IO namespace:

@using System.IO
@using BlazorInputFile

Now add a Razor component to the Pages folder in the client project. Name it Single.razor and replace the existing code with the following:

@page "/singlefile"

<h1>Single file</h1>

<p>A single file input that uploads automatically on file selection</p>

<InputFile OnChange="HandleSelection" />

<p>@status</p>

@code {
    string status;

    async Task HandleSelection(IFileListEntry[] files)
    {
        var file = files.FirstOrDefault();
        if (file != null)
        {
            // Just load into .NET memory to show it can be done
            // Alternatively it could be saved to disk, or parsed in memory, or similar
            var ms = new MemoryStream();
            await file.Data.CopyToAsync(ms);

            status = $"Finished loading {file.Size} bytes from {file.Name}";
        }
    }
}

This is lifted straight from the samples provided by Steve. If you run this page (navigating to /singlefile), the file that you select is loaded into a MemoryStream. And that's all the processing code does. Further processing is left to you.

The next step is to create an endpoint that can receive and process an uploaded file. Add a new Web API controller to the Server application and name it UploadController.cs. Replace the content with the following code:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorWasmTests.Server.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        private readonly IWebHostEnvironment environment;
        public UploadController(IWebHostEnvironment environment)
        {
            this.environment = environment;
        }


        [HttpPost]
        public async Task Post()
        {
            if (HttpContext.Request.Form.Files.Any())
            {
                foreach(var file in HttpContext.Request.Form.Files)
                {
                    var path = Path.Combine(environment.ContentRootPath, "uploads", file.FileName);
                    using (var stream = new FileStream(path, FileMode.Create))
                    {
                        await file.CopyToAsync(stream);
                    }
                }
            }
        }
    }
}

The code should be familiar to anyone who has worked with uploaded files in ASP.NET Core. It checks to see if any files were uploaded, and if any were, they are saved to a folder named uploads. So the next step is to create the uploads folder in the root of the Server project.

Once you have done that, amend the code in the SingleFile.razor file to inject an instance of the HttpClient service, and use it to post the file:

@page "/singlefile"
@inject HttpClient client
<h1>Single file</h1>

<p>A single file input that uploads automatically on file selection</p>

<InputFile OnChange="HandleSelection" />

<p>@status</p>

@code {
    string status;

    async Task HandleSelection(IFileListEntry[] files)
    {
        var file = files.FirstOrDefault();
        if (file != null)
        {
            // Just load into .NET memory to show it can be done
            // Alternatively it could be saved to disk, or parsed in memory, or similar
            var ms = new MemoryStream();
            await file.Data.CopyToAsync(ms);

            status = $"Finished loading {file.Size} bytes from {file.Name}";
            
            var content = new MultipartFormDataContent {
                { new ByteArrayContent(ms.GetBuffer()), "\"upload\"", file.Name }
            };
            await client.PostAsync("upload", content);
        }
    }
}

The alterations are the second line which injects the HttpClient, and the last 4 lines in the HandleSelection method that create an instance of HttpContent, incorporating the file and post it to the Upload controller that you created.

When you run this, ensure that the Server project is set as the Startup project otherwise routing is unlikely to work properly:

Startup Project

You should find that uploaded files are saved to the uploads folder.

Summary

This simple example shows how to create a Web API end point to manage processing of uploaded files on the server, and the minimal amount of code required to post the file content from the Blazor client application to the server.