How to upload a file with Blazor SSR in .NET 8?

April 24, 2024 · 3 minute read · Tags: aspnet | blazor

Blazor has built-in support for handling file uploads.

If you’re using one of the interactive render modes (or .NET 7 or earlier) you can use the dedicated InputFile component and use that to get hold of a file from the user.

You can then save that file somewhere (to disk, or the cloud).

Here’s a basic example using Blazor Server.

@page "/FileUpload/InteractiveServer"
@rendermode  InteractiveServer

<InputFile OnChange="UploadFile" class="form-control"/>
@code {

    private async Task UploadFile(InputFileChangeEventArgs e)
    {
        var saveFolder = Path.Combine("C:\\Temp\\Files");
        var filePath = Path.Combine(saveFolder, e.File.Name);
        Directory.CreateDirectory(saveFolder);

        await using FileStream fs = new(filePath, FileMode.Create);
        await e.File.OpenReadStream().CopyToAsync(fs);
    }

}

Blazor’s built-in InputFile component takes the uploaded file and calls the UploadFile method.

UploadFile creates a new FileStream (for writing the file contents to disk), and opens a ReadStream for the file (to read its contents).

It then copies the file data from the read stream to the file stream and the file is saved.

Making it work with static server-side rendering

.NET 8 offers an alternative way to render Razor components, using static server-side rendering.

With SSR you can render your Razor components on the server. The resulting HTML is then sent as the response to the browser.

In this mode there is no long-running, ongoing interactivity. The component is rendered once, then its job is done.

As a result, we can’t wire up handlers to events such as OnChange (see previous example) because the component isn’t actually running anywhere by the time we’re viewing it in the browser.

The alternative? Turn to the tried and tested way of getting data from user to server, the humble form.

With SSR, we can use a form and the standard HTML file element to take uploaded file data and access it in our component.

First we need the form itself.

@page "/FileUpload"
<h3>Upload File</h3>

<form method="post" enctype="multipart/form-data" @formname="UploadFile">
    <input type="file" name="file" class="form-control"/>
    <button type="submit" class="btn btn-primary mt-3">Upload File</button>
    <AntiforgeryToken/>
</form>

This is a standard HTML form, with a couple of Blazor specific tweaks.

The form needs a @formname

This is so Blazor knows where to route the posted form data when the form is submitted.

Blazor will take the incoming POST request and attempt to locate the corresponding component (based on that formname).

But how to then access that data in our component’s logic?

@code {

    [SupplyParameterFromForm] public IFormFile? File { get; set; }

    protected override async Task OnInitializedAsync()
    {
        _thing = File?.FileName ?? "No File";

        if (File == null)
            return;

        var saveFolder = Path.Combine("C:\\Temp\\Files");
        var filePath = Path.Combine(saveFolder, File.FileName);
        Directory.CreateDirectory(saveFolder);
        await using var stream = new FileStream(filePath, FileMode.Create);
        await File.CopyToAsync(stream);
    }

}

For that we can use the handy [SupplyParameterFromForm] attribute.

With that we can take that incoming form data and bind it to a property in our component.

In this case we’re going to try to bind the value for file in the form data, and bind it to the File property.

When the form is submitted Blazor will locate this component and render it.

At that point the OnIntiializedAsync method will be invoked.

Blazor’s Model binding ensures File has been populated with the details of the uploaded file.

Then we can create a new FileStream and copy the contents of the uploaded file to it, effectively saving the file to the local filesystem (on the server, where the component is running).

Next up

3 simple design tips to improve your Web UI
Spruce up your features
The quickest way to integrate PayPal checkout with Blazor SSR in .NET 8
JavaScript Interop works differently with Blazor Server-side rendering
Interactive what now? Deciphering Blazor’s web app project template options
Create a new Blazor Web App and you’ll be asked how you want interactivity to work, but what does it all mean?