await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
接收图像文件的组件可以对文件调用 BrowserFileExtensions.RequestImageFileAsync 便利方法,在图像流式传入应用之前,在浏览器的 JavaScript 运行时内调整图像数据的大小。 调用 RequestImageFileAsync 的用例最适合 Blazor WebAssembly 应用。
文件大小读取和上传限制
Blazor Server 应用中的 InputFile 组件没有文件大小读取或上传限制。
在发布 ASP.NET Core 6.0 之前,InputFile 组件的文件大小读取限制为 2 GB。 在 ASP.NET Core 6.0 或更高版本中,InputFile 组件没有文件大小读取限制。
在迄今为止的 ASP.NET Core 的所有版本中,Blazor WebAssembly 将数据从 JavaScript 封送到 C# 时,会将文件的字节读取到单个 JavaScript 数组缓冲区中,大小不超过 2 GB 或设备的可用内存。 大型文件上传 (> 250 MB) 可能会失败。
上传文件示例
以下示例演示在组件中上传多个文件。 通过 InputFileChangeEventArgs.GetMultipleFiles,可以读取多个文件。 请指定最大文件数,以防止恶意用户上传的文件数超过应用的预期值。 如果文件上传不支持多个文件,则可以通过 InputFileChangeEventArgs.File 读取第一个文件,并且只能读取此文件。
InputFileChangeEventArgs 位于 Microsoft.AspNetCore.Components.Forms 命名空间,后者通常是应用的 _Imports.razor
文件中的一个命名空间。 当 _Imports.razor
文件中存在命名空间时,它提供对应用组件的 API 成员访问权限:
using Microsoft.AspNetCore.Components.Forms
_Imports.razor
文件中的命名空间不适用于 C# 文件 (.cs
)。 C# 文件需要显式 using
指令。
为了测试文件上传组件,可以使用 PowerShell 创建任意大小的测试文件:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
在上述命令中:
{SIZE}
占位符是文件大小(以字节为单位,例如,2 MB 文件为 2097152
)。
{PATH}
占位符是路径和带有文件扩展名的文件(例如,D:/test_files/testfile2MB.txt
)。
Pages/FileUpload1.razor
:
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Uploading...</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
IBrowserFile 会以属性形式返回浏览器公开的元数据。 使用此元数据进行初步验证。
决不要信任以下属性的值,特别是在 UI 中显示的 Name 属性。 将所有用户提供的数据视为对应用、服务器和网络的重大安全风险。 有关详细信息,请参阅在 ASP.NET Core 中上传文件。
LastModified
ContentType
将文件上传到服务器
以下示例演示了将文件从 Blazor Server 应用上传到单独的应用(可能位于单独的服务器上)中的后端 Web API 控制器。
在 Blazor Server 应用中,添加 IHttpClientFactory 和允许应用创建 HttpClient 实例的相关服务。
在 Program.cs
中:
builder.Services.AddHttpClient();
有关详细信息,请参阅在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求。
对于本部分中的示例:
Web API 在 URL https://localhost:5001
上运行
Blazor Server 应用在 URL https://localhost:5003
上运行
出于测试目的,上述 URL 在项目的 Properties/launchSettings.json
文件中进行配置。
以下示例演示了将文件上传到托管的 Blazor WebAssembly解决方案的 Server 应用中的 Web API 控制器。
执行托管应用 Blazor WebAssembly 时,请从解决方案的 Server 项目运行应用。
上传结果类
以下 UploadResult
类置于客户端项目和 Web API 项目中,以维护上传文件的结果。 如果文件无法上传到服务器上,ErrorCode
中会返回错误代码以向用户显示。 服务器上会针对每个文件生成安全的文件名,并在 StoredFileName
中返回给客户端以供显示。 客户端和服务器之间会使用 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; }
public class UploadResult
public bool Uploaded { get; set; }
public string FileName { get; set; }
public string StoredFileName { get; set; }
public int ErrorCode { get; set; }
Shared 项目中的以下 UploadResult
类维护已上传文件的结果。 如果文件无法上传到服务器上,ErrorCode
中会返回错误代码以向用户显示。 服务器上会针对每个文件生成安全的文件名,并在 StoredFileName
中返回给客户端以供显示。 客户端和服务器之间会使用 FileName
中的不安全/不受信任的文件名对文件进行键控。 在下面的示例中,项目的命名空间为 BlazorSample.Shared
。
托管的 Blazor WebAssembly解决方案的 Shared 项目中的 UploadResult.cs
:
namespace BlazorSample.Shared;
public class UploadResult
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
namespace BlazorSample.Shared
public class UploadResult
public bool Uploaded { get; set; }
public string FileName { get; set; }
public string StoredFileName { get; set; }
public int ErrorCode { get; set; }
若要使 UploadResult
类可用于 Client 项目,请针对 Shared 项目将导入添加到 Client 项目的 _Imports.razor
文件:
@using BlazorSample.Shared
生产应用的最佳安全做法是避免向客户端发送错误消息,这些错误消息可能会披露有关应用、服务器或网络的敏感信息。 提供详细的错误消息会有利于恶意用户针对应用、服务器或网络设计攻击方案。 本部分中的示例代码只发送回错误代码号 (int
),以便发生服务器端错误时供组件客户端显示。 如果用户需要在文件上传方面的帮助,他们会向支持人员提供错误代码以解决支持票证问题,而无需知道错误的确切原因。
以下 FileUpload2
组件:
允许用户从客户端上传文件。
在 UI 中显示由客户端提供的不受信任/不安全的文件名。 不受信任的/不安全的文件名由 Razor 自动进行 HTML 编码,以在 UI 中安全显示。
在以下情况下,不要信任客户端提供的文件名:
将文件保存到文件系统或服务中。
在不自动或通过开发人员代码对文件名进行编码的 UI 中显示。
若要详细了解将文件上传到服务器时的安全注意事项,请参阅在 ASP.NET Core 中上传文件。
Blazor Server 应用中的 Pages/FileUpload2.razor
:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
var options = new JsonSerializerOptions
PropertyNameCaseInsensitive = true,
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string? Name { get; set; }
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
var options = new JsonSerializerOptions
PropertyNameCaseInsensitive = true,
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string? Name { get; set; }
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
var options = new JsonSerializerOptions
PropertyNameCaseInsensitive = true,
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName);
if (result is null)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result = new();
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string Name { get; set; }
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string? Name { get; set; }
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string? Name { get; set; }
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
@if (files.Count > 0)
<div class="card">
<div class="card-body">
@foreach (var file in files)
File: @file.Name
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
Stored File Name: @result.StoredFileName
</span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
files.Add(new() { Name = file.Name });
var fileContent =
new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
catch (Exception ex)
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
if (upload)
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
uploadResults = uploadResults.Concat(newUploadResults).ToList();
shouldRender = true;
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
result = uploadResults.SingleOrDefault(f => f.FileName == fileName);
if (result is null)
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result = new();
result.ErrorCode = 5;
return result.Uploaded;
private class File
public string Name { get; set; }
上传控制器
Web API 项目中的以下控制器保存从客户端上传的文件。
本部分中的控制器用于独立于 Blazor Server 应用的 Web API 项目。
ASP.NET Core 6.0 中的最小 API 本机不支持绑定具有 [FromForm]
属性的表单值。 因此,无法将以下 Filesave
控制器示例转换为使用最小 API。 ASP.NET Core 7.0 或更高版本提供了对使用最小 API 绑定表单值的支持。
若要使用以下代码,请为于 Development
环境中运行的应用在 Web API 项目的根上创建 Development/unsafe_uploads
文件夹。
由于该示例使用应用的环境作为保存文件的路径的一部分,因此,如果在测试和生产中使用其他环境,则还需要其他文件夹。 例如,为 Staging
环境创建 Staging/unsafe_uploads
文件夹。 为 Production
环境创建 Production/unsafe_uploads
文件夹。
此示例会保存文件,但不会扫描其内容,本文中的指南不考虑已上传文件的其他最佳安全做法。 在暂存和生产系统上,禁用对上传文件夹的执行权限,并在上传后立即使用防病毒/反恶意软件扫描程序 API 扫描文件。 有关详细信息,请参阅在 ASP.NET Core 中上传文件。
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
private readonly IWebHostEnvironment env;
private readonly ILogger<FilesaveController> logger;
public FilesaveController(IWebHostEnvironment 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;
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++;
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);
Server 项目中的以下控制器保存从客户端上传的文件。
ASP.NET Core 6.0 中的最小 API 本机不支持绑定具有 [FromForm]
属性的表单值。 因此,无法将以下 Filesave
控制器示例转换为使用最小 API。 ASP.NET Core 7.0 或更高版本提供了对使用最小 API 绑定表单值的支持。
若要使用以下代码,请为于 Development
环境中运行的应用在 Server 项目的根目录下创建一个 Development/unsafe_uploads
文件夹。
由于该示例使用应用的环境作为保存文件的路径的一部分,因此,如果在测试和生产中使用其他环境,则还需要其他文件夹。 例如,为 Staging
环境创建 Staging/unsafe_uploads
文件夹。 为 Production
环境创建 Production/unsafe_uploads
文件夹。
此示例会保存文件,但不会扫描其内容,本文中的指南不考虑已上传文件的其他最佳安全做法。 在暂存和生产系统上,禁用对上传文件夹的执行权限,并在上传后立即使用防病毒/反恶意软件扫描程序 API 扫描文件。 有关详细信息,请参阅在 ASP.NET Core 中上传文件。
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;
[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
private readonly IWebHostEnvironment env;
private readonly ILogger<FilesaveController> logger;
public FilesaveController(IWebHostEnvironment 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;
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++;
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);
前面的代码调用 GetRandomFileName 来生成安全文件名。 永远不要信任浏览器提供的文件名,因为攻击者可能会选择覆盖现有文件的现有文件名,或者发送尝试在应用外写入的路径。
取消文件上传
文件上传组件可以通过在调用 IBrowserFile.OpenReadStream 或 StreamReader.ReadAsync 时使用 CancellationToken 检测用户何时取消上传。
为 InputFile
组件创建 CancellationTokenSource。 在 OnInputFileChange
方法开始时,检查以前的上传是否正在进行。
如果文件上传正在进行:
在上次上传时调用 Cancel。
为下次上传创建新的 CancellationTokenSource,然后将 CancellationTokenSource.Token 传递到 OpenReadStream 或 ReadAsync。
上传文件,显示进度
下面的示例展示了如何在 Blazor Server 应用中上传文件,同时向用户显示上传进度。
若要在测试应用中使用以下示例:
创建文件夹来保存已上传用于 Development
环境的文件:Development/unsafe_uploads
。
配置最大文件夹大小(maxFileSize
在下例中是 15 KB)和允许的文件最大数量(maxAllowedFiles
在下例中是 3)。
如果需要,请将缓冲区设置为其他值(在下例中是 10 KB),以提高进度报告中的粒度。 由于性能和安全方面的问题,建议不要使用大于 30 KB 的缓冲区。
此示例会保存文件,但不会扫描其内容,本文中的指南不考虑已上传文件的其他最佳安全做法。 在暂存和生产系统上,禁用对上传文件夹的执行权限,并在上传后立即使用防病毒/反恶意软件扫描程序 API 扫描文件。 有关详细信息,请参阅在 ASP.NET Core 中上传文件。
Pages/FileUpload3.razor
:
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
@if (isLoading)
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
@foreach (var file in loadedFiles)
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
loadedFiles.Add(file);
catch (Exception ex)
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
isLoading = false;
有关详细信息,请参见以下 API 资源:
FileStream:为文件提供 Stream,既支持同步读写操作,也支持异步读写操作。
FileStream.ReadAsync:先前 FileUpload3
组件通过 ReadAsync 异步读取流。 Razor 组件不支持通过 Read 异步读取流。
在 Blazor Server 中,读取文件时,文件数据通过 SignalR 连接流式传入服务器上的 .NET 代码。
通过 RemoteBrowserFileStreamOptions,可以配置 Blazor Server 的文件上传特性。
在 Blazor WebAssembly 中,文件数据直接流式传入浏览器中的 .NET 代码。
上传图像预览
对于上传图像的图像预览,首先添加具有组件引用和 OnChange
处理程序的 InputFile
组件:
<InputFile @ref="inputFile" OnChange="@ShowPreview" />
添加包含元素引用的图像元素,该元素用作图像预览的占位符:
<img @ref="previewImageElem" />
添加关联的引用:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
在 JavaScript 中,添加一个使用 HTML input
和 img
元素调用的函数,该函数执行以下操作:
提取所选文件。
使用 createObjectURL
创建对象 URL。
将事件侦听器设置为在加载图像后使用 revokeObjectURL
撤销对象 URL,以便内存不会泄漏。
设置 img
元素的源以显示图像。
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
最后,使用注入的 IJSRuntime 添加调用 JavaScript 函数的 OnChange
处理程序:
@inject IJSRuntime JS
@code {
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
前面的示例用于上传单个图像。 可以扩展此方法以支持 multiple
图像。
以下 FileUpload4
组件显示了完整示例。
Pages/FileUpload4.razor
:
@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);
将文件上传到外部服务
客户端可以直接将文件上传到外部服务,而不是由应用处理文件上传字节以及由应用的服务器接收上传的文件。 应用可以根据需要,安全地处理来自外部服务的文件。 这种方法加强了应用及其服务器对恶意攻击和潜在性能问题的防范。
考虑使用 Azure 文件存储、Azure Blob 存储或第三方服务的方法,以提供以下潜在优势:
使用 JavaScript 客户端库或 REST API 将文件从客户端直接上传到外部服务。 例如,Azure 提供了以下客户端库和 API: