How to upload a file with Blazor SSR in .NET 8?
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).