title | author | description | monikerRange | ms.author | ms.custom | ms.date | uid |
---|---|---|---|---|---|---|---|
ASP.NET Core Blazor file uploads |
guardrex |
Learn how to upload files in Blazor with the InputFile component. |
>= aspnetcore-5.0 |
riande |
mvc |
08/29/2024 |
blazor/file-uploads |
This article explains how to upload files in Blazor with the xref:Microsoft.AspNetCore.Components.Forms.InputFile component.
Warning
Always follow security best practices when permitting users to upload files. For more information, see xref:mvc/models/file-uploads#security-considerations.
Use the xref:Microsoft.AspNetCore.Components.Forms.InputFile component to read browser file data into .NET code. The xref:Microsoft.AspNetCore.Components.Forms.InputFile component renders an HTML <input>
element of type file
for single file uploads. Add the multiple
attribute to permit the user to upload multiple files at once.
File selection isn't cumulative when using an xref:Microsoft.AspNetCore.Components.Forms.InputFile component or its underlying HTML <input type="file">
, so you can't add files to an existing file selection. The component always replaces the user's initial file selection, so file references from prior selections aren't available.
The following xref:Microsoft.AspNetCore.Components.Forms.InputFile component executes the LoadFiles
method when the xref:Microsoft.AspNetCore.Components.Forms.InputFile.OnChange (change
) event occurs. An xref:Microsoft.AspNetCore.Components.Forms.InputFileChangeEventArgs provides access to the selected file list and details about each file:
<InputFile OnChange="LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
Rendered HTML:
<input multiple="" type="file" _bl_2="">
Note
In the preceding example, the <input>
element's _bl_2
attribute is used for Blazor's internal processing.
To read data from a user-selected file, call xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A?displayProperty=nameWithType on the file and read from the returned stream. For more information, see the File streams section.
xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A enforces a maximum size in bytes of its xref:System.IO.Stream. Reading one file or multiple files larger than 500 KB results in an exception. This limit prevents developers from accidentally reading large files into memory. The maxAllowedSize
parameter of xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A can be used to specify a larger size if required.
If you need access to a xref:System.IO.Stream that represents the file's bytes, use xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A?displayProperty=nameWithType. Avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a xref:System.IO.MemoryStream or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential Denial of Service (DoS) risk, especially for server-side components. Instead, consider adopting either of the following approaches:
- Copy the stream directly to a file on disk without reading it into memory. Note that Blazor apps executing code on the server aren't able to access the client's file system directly.
- Upload files from the client directly to an external service. For more information, see the Upload files to an external service section.
In the following examples, browserFile
represents the uploaded file and implements xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile. Working implementations for xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile are shown in the file upload components later in this article.
✔️Supported: The following approach is recommended because the file's xref:System.IO.Stream is provided directly to the consumer, a xref:System.IO.FileStream that creates the file at the provided path:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);
✔️Supported: The following approach is recommended for Microsoft Azure Blob Storage because the file's xref:System.IO.Stream is provided directly to xref:Azure.Storage.Blobs.BlobContainerClient.UploadBlobAsync%2A:
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
❌Not recommended: The following approach is NOT recommended because the file's xref:System.IO.Stream content is read into a xref:System.String in memory (reader
):
var reader =
await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();
❌Not recommended: The following approach is NOT recommended for Microsoft Azure Blob Storage because the file's xref:System.IO.Stream content is copied into a xref:System.IO.MemoryStream in memory (memoryStream
) before calling xref:Azure.Storage.Blobs.BlobContainerClient.UploadBlobAsync%2A:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
A component that receives an image file can call the xref:Microsoft.AspNetCore.Components.Forms.BrowserFileExtensions.RequestImageFileAsync%2A?displayProperty=nameWithType convenience method on the file to resize the image data within the browser's JavaScript runtime before the image is streamed into the app. Use cases for calling xref:Microsoft.AspNetCore.Components.Forms.BrowserFileExtensions.RequestImageFileAsync%2A are most appropriate for Blazor WebAssembly apps.
:::moniker range="< aspnetcore-9.0"
If you're using the Autofac Inversion of Control (IoC) container instead of the built-in ASP.NET Core dependency injection container, set xref:Microsoft.AspNetCore.SignalR.HubOptions.DisableImplicitFromServicesParameters%2A to true
in the server-side circuit handler hub options. For more information, see FileUpload: Did not receive any data in the allotted time (dotnet/aspnetcore
#38842).
:::moniker-end
:::moniker range=">= aspnetcore-9.0"
Server-side or client-side, there's no file read or upload size limit for the xref:Microsoft.AspNetCore.Components.Forms.InputFile component.
For Chromium-based browsers (Google Chrome and Microsoft Edge) using the HTTP/2 protocol, client-side Blazor uses Streams API to permit uploading large files (> 2 GB or larger than the device's available memory).
For non-Chromium browsers or without HTTP/2 protocol, client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshalling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory. Large file uploads (> 250 MB) may fail for client-side uploads using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component. For more information, see the following discussions:
- The Blazor InputFile Component should handle chunking when the file is uploaded (dotnet/runtime #84685)
- Request Streaming upload via http handler (dotnet/runtime #36634)
For large client-side file uploads that fail when attempting to use the xref:Microsoft.AspNetCore.Components.Forms.InputFile component, we recommend chunking large files with a custom component using multiple HTTP range requests instead of using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component.
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-9.0"
Server-side or client-side, there's no file read or upload size limit specifically for the xref:Microsoft.AspNetCore.Components.Forms.InputFile component. However, client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshalling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory. Large file uploads (> 250 MB) may fail for client-side uploads using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component. For more information, see the following discussions:
- The Blazor InputFile Component should handle chunking when the file is uploaded (dotnet/runtime #84685)
- Request Streaming upload via http handler (dotnet/runtime #36634)
For large client-side file uploads that fail when attempting to use the xref:Microsoft.AspNetCore.Components.Forms.InputFile component, we recommend:
- Adopting .NET 9 or later for Chromium-based browsers (Google Chrome and Microsoft Edge) with HTTP/2 protocol support, where this problem is internally addressed by Blazor with Streams API.
- For non-Chromium browsers, Chromium browsers without HTTP/2, or .NET 8 or earlier, chunking large files with a custom component using multiple HTTP range requests instead of using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component.
:::moniker-end
:::moniker range="< aspnetcore-6.0"
The maximum supported file size for the xref:Microsoft.AspNetCore.Components.Forms.InputFile component is 2 GB. Additionally, client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshalling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory. Large file uploads (> 250 MB) may fail for client-side uploads using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component. For more information, see the following discussions:
-
Request Streaming upload via http handler (dotnet/runtime #36634)
-
Adopting .NET 9 or later for Chromium-based browsers (Google Chrome and Microsoft Edge) with HTTP/2 protocol support, where this problem is internally addressed by Blazor with Streams API.
-
For non-Chromium browsers, Chromium browsers without HTTP/2, or .NET 5 or earlier, chunking large files with a custom component using multiple HTTP range requests instead of using the xref:Microsoft.AspNetCore.Components.Forms.InputFile component.
:::moniker-end
The following examples demonstrate multiple file upload in a component. xref:Microsoft.AspNetCore.Components.Forms.InputFileChangeEventArgs.GetMultipleFiles%2A?displayProperty=nameWithType allows reading multiple files. Specify the maximum number of files to prevent a malicious user from uploading a larger number of files than the app expects. xref:Microsoft.AspNetCore.Components.Forms.InputFileChangeEventArgs.File?displayProperty=nameWithType allows reading the first and only file if the file upload doesn't support multiple files.
xref:Microsoft.AspNetCore.Components.Forms.InputFileChangeEventArgs is in the xref:Microsoft.AspNetCore.Components.Forms?displayProperty=fullName namespace, which is typically one of the namespaces in the app's _Imports.razor
file. When the namespace is present in the _Imports.razor
file, it provides API member access to the app's components.
Namespaces in the _Imports.razor
file aren't applied to C# files (.cs
). C# files require an explicit using
directive at the top of the class file:
using Microsoft.AspNetCore.Components.Forms;
For testing file upload components, you can create test files of any size with PowerShell:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
In the preceding command:
- The
{SIZE}
placeholder is the size of the file in bytes (for example,2097152
for a 2 MB file). - The
{PATH}
placeholder is the path and file with file extension (for example,D:/test_files/testfile2MB.txt
).
To use the following code, create a Development/unsafe_uploads
folder at the root of the app running in the Development
environment.
Because the example uses the app's environment as part of the path where files are saved, additional folders are required if other environments are used in testing and production. For example, create a Staging/unsafe_uploads
folder for the Staging
environment. Create a Production/unsafe_uploads
folder for the Production
environment.
Warning
The example saves files without scanning their contents, and the guidance in this article doesn't take into account additional security best practices for uploaded files. On staging and production systems, disable execute permission on the upload folder and scan files with an anti-virus/anti-malware scanner API immediately after upload. For more information, see xref:mvc/models/file-uploads#security-considerations.
FileUpload1.razor
:
:::moniker range=">= aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/FileUpload1.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_Server/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
The following example processes file bytes and doesn't send files to a destination outside of the app. For an example of a Razor component that sends a file to a server or service, see the following sections:
The component assumes that the Interactive WebAssembly render mode (InteractiveWebAssembly
) is inherited from a parent component or applied globally to the app.
:::moniker range=">= aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_WebAssembly/Pages/FileUpload1.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload1.razor":::
:::moniker-end
xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile returns metadata exposed by the browser as properties. Use this metadata for preliminary validation.
- xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.Name
- xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.Size
- xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.LastModified
- xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.ContentType
Never trust the values of the preceding properties, especially the xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.Name property for display in the UI. Treat all user-supplied data as a significant security risk to the app, server, and network. For more information, see xref:mvc/models/file-uploads#security-considerations.
This section applies to Interactive Server components in Blazor Web Apps or Blazor Server apps.
The following example demonstrates uploading files from a server-side app to a backend web API controller in a separate app, possibly on a separate server.
In the server-side app's Program
file, add xref:System.Net.Http.IHttpClientFactory and related services that allow the app to create xref:System.Net.Http.HttpClient instances:
builder.Services.AddHttpClient();
For more information, see xref:fundamentals/http-requests.
For the examples in this section:
- The web API runs at the URL:
https://localhost:5001
- The server-side app runs at the URL:
https://localhost:5003
For testing, the preceding URLs are configured in the projects' Properties/launchSettings.json
files.
The following UploadResult
class maintains the result of an uploaded file. When a file fails to upload on the server, an error code is returned in ErrorCode
for display to the user. A safe file name is generated on the server for each file and returned to the client in StoredFileName
for display. Files are keyed between the client and server using the unsafe/untrusted file name in FileName
.
UploadResult.cs
:
public class UploadResult
{
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
}
A security best practice for production apps is to avoid sending error messages to clients that might reveal sensitive information about an app, server, or network. Providing detailed error messages can aid a malicious user in devising attacks on an app, server, or network. The example code in this section only sends back an error code number (int
) for display by the component client-side if a server-side error occurs. If a user requires assistance with a file upload, they provide the error code to support personnel for support ticket resolution without ever knowing the exact cause of the error.
The following LazyBrowserFileStream
class defines a custom stream type that lazily calls xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A just before the first bytes of the stream are requested. The stream isn't transmitted from the browser to the server until reading the stream begins in .NET.
LazyBrowserFileStream.cs
:
:::moniker range=">= aspnetcore-8.0"
:::code language="csharp" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/LazyBrowserFileStream.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_Server/LazyBrowserFileStream.cs":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_Server/LazyBrowserFileStream.cs":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_Server/LazyBrowserFileStream.cs":::
:::moniker-end
The following FileUpload2
component:
- Permits users to upload files from the client.
- Displays the untrusted/unsafe file name provided by the client in the UI. The untrusted/unsafe file name is automatically HTML-encoded by Razor for safe display in the UI.
Warning
Don't trust file names supplied by clients for:
- Saving the file to a file system or service.
- Display in UIs that don't encode file names automatically or via developer code.
For more information on security considerations when uploading files to a server, see xref:mvc/models/file-uploads#security-considerations.
FileUpload2.razor
:
:::moniker range=">= aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/FileUpload2.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_Server/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
If the component limits file uploads to a single file at a time or if the component only adopts interactive client-side rendering (CSR, InteractiveWebAssembly
), the component can avoid the use of the LazyBrowserFileStream
and use a xref:System.IO.Stream. The following demonstrates the changes for the FileUpload2
component:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remove the LazyBrowserFileStream
class (LazyBrowserFileStream.cs
), as it isn't used.
:::moniker-end
:::moniker range="< aspnetcore-8.0"
If the component limits file uploads to a single file at a time, the component can avoid the use of the LazyBrowserFileStream
and use a xref:System.IO.Stream. The following demonstrates the changes for the FileUpload2
component:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remove the LazyBrowserFileStream
class (LazyBrowserFileStream.cs
), as it isn't used.
:::moniker-end
The following controller in the web API project saves uploaded files from the client.
Important
The controller in this section is intended for use in a separate web API project from the Blazor app. The web API should mitigate Cross-Site Request Forgery (XSRF/CSRF) attacks if file upload users are authenticated.
:::moniker range="= aspnetcore-6.0"
Note
Binding form values with the [FromForm]
attribute isn't natively supported for Minimal APIs in ASP.NET Core in .NET 6. Therefore, the following Filesave
controller example can't be converted to use Minimal APIs. Support for binding from form values with Minimal APIs is available in ASP.NET Core in .NET 7 or later.
:::moniker-end
To use the following code, create a Development/unsafe_uploads
folder at the root of the web API project for the app running in the Development
environment.
Because the example uses the app's environment as part of the path where files are saved, additional folders are required if other environments are used in testing and production. For example, create a Staging/unsafe_uploads
folder for the Staging
environment. Create a Production/unsafe_uploads
folder for the Production
environment.
Warning
The example saves files without scanning their contents, and the guidance in this article doesn't take into account additional security best practices for uploaded files. On staging and production systems, disable execute permission on the upload folder and scan files with an anti-virus/anti-malware scanner API immediately after upload. For more information, see xref:mvc/models/file-uploads#security-considerations.
Controllers/FilesaveController.cs
:
using System.Net;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
private readonly IHostEnvironment env;
private readonly ILogger<FilesaveController> logger;
public FilesaveController(IHostEnvironment env,
ILogger<FilesaveController> logger)
{
this.env = env;
this.logger = logger;
}
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = new();
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
In the preceding code, xref:System.IO.Path.GetRandomFileName%2A is called to generate a secure file name. Never trust the file name provided by the browser, as an attacker may choose an existing file name that overwrites an existing file or send a path that attempts to write outside of the app.
The server app must register controller services and map controller endpoints. For more information, see xref:mvc/controllers/routing.
This section applies to client-side rendered (CSR) components in Blazor Web Apps or Blazor WebAssembly apps.
The following example demonstrates uploading files to a backend web API controller in a separate app, possibly on a separate server, from a component in a Blazor Web App that adopts CSR or a component in a Blazor WebAssembly app.
The following UploadResult
class maintains the result of an uploaded file. When a file fails to upload on the server, an error code is returned in ErrorCode
for display to the user. A safe file name is generated on the server for each file and returned to the client in StoredFileName
for display. Files are keyed between the client and server using the unsafe/untrusted file name in FileName
.
UploadResult.cs
:
public class UploadResult
{
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
}
Note
The preceding UploadResult
class can be shared between client- and server-based projects. When client and server projects share the class, add an import to each project's _Imports.razor
file for the shared project. For example:
@using BlazorSample.Shared
The following FileUpload2
component:
- Permits users to upload files from the client.
- Displays the untrusted/unsafe file name provided by the client in the UI. The untrusted/unsafe file name is automatically HTML-encoded by Razor for safe display in the UI.
A security best practice for production apps is to avoid sending error messages to clients that might reveal sensitive information about an app, server, or network. Providing detailed error messages can aid a malicious user in devising attacks on an app, server, or network. The example code in this section only sends back an error code number (int
) for display by the component client-side if a server-side error occurs. If a user requires assistance with a file upload, they provide the error code to support personnel for support ticket resolution without ever knowing the exact cause of the error.
Warning
Don't trust file names supplied by clients for:
- Saving the file to a file system or service.
- Display in UIs that don't encode file names automatically or via developer code.
For more information on security considerations when uploading files to a server, see xref:mvc/models/file-uploads#security-considerations.
:::moniker range=">= aspnetcore-8.0"
In the Blazor Web App main project, add xref:System.Net.Http.IHttpClientFactory and related services in the project's Program
file:
builder.Services.AddHttpClient();
The HttpClient
services must be added to the main project because the client-side component is prerendered on the server. If you disable prerendering for the following component, you aren't required to provide the HttpClient
services in the main app and don't need to add the preceding line to the main project.
For more information on adding HttpClient
services to an ASP.NET Core app, see xref:fundamentals/http-requests.
The client project (.Client
) of a Blazor Web App must also register an xref:System.Net.Http.HttpClient for HTTP POST requests to a backend web API controller. Confirm or add the following to the client project's Program
file:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
The preceding example sets the base address with builder.HostEnvironment.BaseAddress
(xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment.BaseAddress%2A?displayProperty=nameWithType), which gets the base address for the app and is typically derived from the <base>
tag's href
value in the host page. If you're calling an external web API, set the URI to the web API's base address.
Specify the Interactive WebAssembly render mode attribute at the top of the following component in a Blazor Web App:
@rendermode InteractiveWebAssembly
:::moniker-end
FileUpload2.razor
:
:::moniker range=">= aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_WebAssembly/Pages/FileUpload2.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/file-uploads/FileUpload2.razor":::
:::moniker-end
The following controller in the server-side project saves uploaded files from the client.
:::moniker range="< aspnetcore-6.0"
Note
Binding form values with the [FromForm]
attribute isn't natively supported for Minimal APIs in ASP.NET Core in .NET 6. Therefore, the following Filesave
controller example can't be converted to use Minimal APIs. Support for binding from form values with Minimal APIs is available in ASP.NET Core in .NET 7 or later.
:::moniker-end
To use the following code, create a Development/unsafe_uploads
folder at the root of the server-side project for the app running in the Development
environment.
Because the example uses the app's environment as part of the path where files are saved, additional folders are required if other environments are used in testing and production. For example, create a Staging/unsafe_uploads
folder for the Staging
environment. Create a Production/unsafe_uploads
folder for the Production
environment.
Warning
The example saves files without scanning their contents, and the guidance in this article doesn't take into account additional security best practices for uploaded files. On staging and production systems, disable execute permission on the upload folder and scan files with an anti-virus/anti-malware scanner API immediately after upload. For more information, see xref:mvc/models/file-uploads#security-considerations.
In the following example, update the shared project's namespace to match the shared project if a shared project is supplying the UploadResult
class.
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;
[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
private readonly IHostEnvironment env;
private readonly ILogger<FilesaveController> logger;
public FilesaveController(IHostEnvironment env,
ILogger<FilesaveController> logger)
{
this.env = env;
this.logger = logger;
}
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = new();
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
In the preceding code, xref:System.IO.Path.GetRandomFileName%2A is called to generate a secure file name. Never trust the file name provided by the browser, as an attacker may choose an existing file name that overwrites an existing file or send a path that attempts to write outside of the app.
The server app must register controller services and map controller endpoints. For more information, see xref:mvc/controllers/routing.
A file upload component can detect when a user has cancelled an upload by using a xref:System.Threading.CancellationToken when calling into the xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A?displayProperty=nameWithType or xref:System.IO.StreamReader.ReadAsync%2A?displayProperty=nameWithType.
Create a xref:System.Threading.CancellationTokenSource for the InputFile
component. At the start of the OnInputFileChange
method, check if a previous upload is in progress.
If a file upload is in progress:
- Call xref:System.Threading.CancellationTokenSource.Cancel%2A on the previous upload.
- Create a new xref:System.Threading.CancellationTokenSource for the next upload and pass the xref:System.Threading.CancellationTokenSource.Token?displayProperty=nameWithType to xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A or xref:System.IO.StreamReader.ReadAsync%2A.
The following example demonstrates how to upload files in a server-side app with upload progress displayed to the user.
To use the following example in a test app:
- Create a folder to save uploaded files for the
Development
environment:Development/unsafe_uploads
. - Configure the maximum file size (
maxFileSize
, 15 KB in the following example) and maximum number of allowed files (maxAllowedFiles
, 3 in the following example). - Set the buffer to a different value (10 KB in the following example), if desired, for increased granularity in progress reporting. We don't recommended using a buffer larger than 30 KB due to performance and security concerns.
Warning
The example saves files without scanning their contents, and the guidance in this article doesn't take into account additional security best practices for uploaded files. On staging and production systems, disable execute permission on the upload folder and scan files with an anti-virus/anti-malware scanner API immediately after upload. For more information, see xref:mvc/models/file-uploads#security-considerations.
FileUpload3.razor
:
:::moniker range=">= aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/FileUpload3.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor":::
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor":::
:::moniker-end
:::moniker range="< aspnetcore-6.0"
:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor":::
:::moniker-end
For more information, see the following API resources:
- xref:System.IO.FileStream: Provides a xref:System.IO.Stream for a file, supporting both synchronous and asynchronous read and write operations.
- xref:System.IO.FileStream.ReadAsync%2A?displayProperty=nameWithType: The preceding
FileUpload3
component reads the stream asynchronously with xref:System.IO.FileStream.ReadAsync%2A. Reading a stream synchronously with xref:System.IO.FileStream.Read%2A isn't supported in Razor components.
With server interactivity, file data is streamed over the SignalR connection into .NET code on the server as the file is read.
:::moniker range="= aspnetcore-5.0"
xref:Microsoft.AspNetCore.Components.Forms.RemoteBrowserFileStreamOptions allows configuring file upload characteristics.
:::moniker-end
For a WebAssembly-rendered component, file data is streamed directly into the .NET code within the browser.
:::moniker range=">= aspnetcore-6.0"
For an image preview of uploading images, start by adding an InputFile
component with a component reference and an OnChange
handler:
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Add an image element with an element reference, which serves as the placeholder for the image preview:
<img @ref="previewImageElem" />
Add the associated references:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}
In JavaScript, add a function called with an HTML input
and img
element that performs the following:
- Extracts the selected file.
- Creates an object URL with
createObjectURL
. - Sets an event listener to revoke the object URL with
revokeObjectURL
after the image is loaded, so memory isn't leaked. - Sets the
img
element's source to display the image.
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
}
Finally, use an injected xref:Microsoft.JSInterop.IJSRuntime to add the OnChange
handler that calls the JavaScript function:
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
The preceding example is for uploading a single image. The approach can be expanded to support multiple
images.
The following FileUpload4
component shows the complete example.
FileUpload4.razor
:
:::moniker-end
:::moniker range=">= aspnetcore-8.0"
@page "/file-upload-4"
@inject IJSRuntime JS
<h1>File Upload Example</h1>
<InputFile @ref="inputFile" OnChange="ShowPreview" />
<img style="max-width:200px;max-height:200px" @ref="previewImageElem" />
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
:::moniker-end
:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0"
@page "/file-upload-4"
@inject IJSRuntime JS
<h1>File Upload Example</h1>
<InputFile @ref="inputFile" OnChange="ShowPreview" />
<img style="max-width:200px;max-height:200px" @ref="previewImageElem" />
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
:::moniker-end
Instead of an app handling file upload bytes and the app's server receiving uploaded files, clients can directly upload files to an external service. The app can safely process the files from the external service on demand. This approach hardens the app and its server against malicious attacks and potential performance problems.
Consider an approach that uses Azure Files, Azure Blob Storage, or a third-party service with the following potential benefits:
- Upload files from the client directly to an external service with a JavaScript client library or REST API. For example, Azure offers the following client libraries and APIs:
- Authorize user uploads with a user-delegated shared-access signature (SAS) token generated by the app (server-side) for each client file upload. For example, Azure offers the following SAS features:
- Provide automatic redundancy and file share backup.
- Limit uploads with quotas. Note that Azure Blob Storage's quotas are set at the account level, not the container level. However, Azure Files quotas are at the file share level and might provide better control over upload limits. For more information, see the Azure documents linked earlier in this list.
- Secure files with server-side encryption (SSE).
For more information on Azure Blob Storage and Azure Files, see the Azure Storage documentation.
File uploads may fail even before they start, when Blazor retrieves data about the files that exceeds the maximum SignalR message size.
SignalR defines a message size limit that applies to every message Blazor receives, and the xref:Microsoft.AspNetCore.Components.Forms.InputFile component streams files to the server in messages that respect the configured limit. However, the first message, which indicates the set of files to upload, is sent as a unique single message. The size of the first message may exceed the SignalR message size limit. The issue isn't related to the size of the files, it's related to the number of files.
The logged error is similar to the following:
:::no-loc text="Error: Connection disconnected with error 'Error: Server returned an error on close: Connection closed with an error.'. e.log @ blazor.server.js:1":::
When uploading files, reaching the message size limit on the first message is rare. If the limit is reached, the app can configure xref:Microsoft.AspNetCore.SignalR.HubOptions.MaximumReceiveMessageSize?displayProperty=nameWithType with a larger value.
For more information on SignalR configuration and how to set xref:Microsoft.AspNetCore.SignalR.HubOptions.MaximumReceiveMessageSize, see xref:blazor/fundamentals/signalr#server-side-circuit-handler-options.
Blazor relies on xref:Microsoft.AspNetCore.SignalR.HubOptions.MaximumParallelInvocationsPerClient%2A set to 1, which is the default value.
Increasing the value leads to a high probability that CopyTo
operations throw System.InvalidOperationException: 'Reading is not allowed after reader was completed.'
. For more information, see MaximumParallelInvocationsPerClient > 1 breaks file upload in Blazor Server mode (dotnet/aspnetcore
#53951).
The line that calls xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A?displayProperty=nameWithType throws a xref:System.TimeoutException?displayProperty=nameWithType:
:::no-loc text="System.TimeoutException: Did not receive any data in the allotted time.":::
Possible causes:
-
Using the Autofac Inversion of Control (IoC) container instead of the built-in ASP.NET Core dependency injection container. To resolve the issue, set xref:Microsoft.AspNetCore.SignalR.HubOptions.DisableImplicitFromServicesParameters%2A to
true
in the server-side circuit handler hub options. For more information, see FileUpload: Did not receive any data in the allotted time (dotnet/aspnetcore
#38842). -
Not reading the stream to completion. This isn't a framework issue. Trap the exception and investigate it further in your local environment/network.
- Using server-side rendering and calling xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A on multiple files before reading them to completion. To resolve the issue, use the
LazyBrowserFileStream
class and approach described in the Upload files to a server with server-side rendering section of this article.
:::moniker range=">= aspnetcore-6.0"
- xref:blazor/file-downloads
- xref:mvc/models/file-uploads#security-considerations
- xref:blazor/forms/index
- Blazor samples GitHub repository (
dotnet/blazor-samples
) (how to download)
:::moniker-end
:::moniker range="< aspnetcore-6.0"
- xref:mvc/models/file-uploads#security-considerations
- xref:blazor/forms/index
- Blazor samples GitHub repository (
dotnet/blazor-samples
) (how to download)
:::moniker-end