相关文章推荐
重情义的蟠桃  ·  java ...·  昨天    · 
买醉的野马  ·  HttpURLConnection下载文件, ...·  10 小时前    · 
犯傻的蟠桃  ·  nginx post转get 并加参数 ...·  6 小时前    · 
沉稳的冰棍  ·  C + + calling the ...·  51 分钟前    · 
曾深爱过的黄瓜  ·  mysql ...·  2 月前    · 
愉快的匕首  ·  spring-jcl 日志学习 - ...·  1 年前    · 
大方的长颈鹿  ·  DAY26、 Discord bot - ...·  1 年前    · 

允许用户上传文件时,始终遵循最佳安全做法。 有关详细信息,请参阅 在 ASP.NET Core 中上传文件

使用 InputFile 组件将浏览器文件数据读入 .NET 代码。 InputFile 组件呈现 file 类型的 HTML <input> 元素。 默认情况下,用户选择单个文件。 可添加 multiple 属性以允许用户一次上传多个文件。

使用 InputFile 组件或其基础 HTML <input type="file"> 时,文件选择不是累积的,因此无法将文件添加到现有文件选择。 组件始终替换用户的初始文件选择,因此先前选择的文件引用不可用。

发生 OnChange ( change ) 事件时,以下 InputFile 组件执行 LoadFiles 方法。 InputFileChangeEventArgs 提供对所选文件列表和每个文件的详细信息的访问:

<InputFile OnChange="@LoadFiles" multiple />
@code {
    private void LoadFiles(InputFileChangeEventArgs e)

已呈现 HTML:

<input multiple="" type="file" _bl_2="">

在上一个示例中,<input> 元素的 _bl_2 属性用于 Blazor 的内部处理。

若要从用户选择的文件中读取数据,请对该文件调用 IBrowserFile.OpenReadStream,并从返回的流中读取。 有关详细信息,请参阅文件流部分。

OpenReadStream 强制采用其 Stream 的最大大小(以字节为单位)。 读取一个或多个大于 500 KB 的文件会引发异常。 此限制可防止开发人员意外地将大型文件读入到内存中。 如果需要,可以使用 OpenReadStream 上的 maxAllowedSize 参数指定更大的大小。

若需要访问表示文件字节的 Stream,请使用 IBrowserFile.OpenReadStream。 避免将传入的文件流一次直接读入到内存中。 例如,不要将文件的所有字节复制到 MemoryStream,也不要将整个流一次读入到字节数组中。 这些方法可能会导致性能和安全问题,尤其是对于 Blazor Server 应用。 请改为考虑采用下列方法之一:

  • 在托管 Blazor WebAssembly 应用或 Blazor Server 应用的服务器上,将流直接复制到磁盘上的文件,而不将它读入到内存中。 请注意,Blazor 应用不能直接访问客户端的文件系统。
  • 将文件从客户端直接上传到外部服务。 有关详细信息,请参阅将文件上传到外部服务部分。
  • 在以下示例中,browserFile 表示上传的文件,并实现 IBrowserFileIBrowserFile 的工作实现显示在本文后面的文件上传组件中。

    不支持:不建议使用以下方法,因为会将文件的 Stream 内容读入内存 (reader) 中的 String

    var reader = 
        await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();
    

    不支持:不建议对 Microsoft Azure Blob 存储使用以下方法,因为在调用 UploadBlobAsync 之前,会将文件的 Stream 内容复制到内存 (memoryStream) 中的 MemoryStream

    var memoryStream = new MemoryStream();
    browserFile.OpenReadStream().CopyToAsync(memoryStream);
    await blobContainerClient.UploadBlobAsync(
        trustedFileName, memoryStream));
    

    支持:建议使用以下方法,因为文件的 Stream 是直接提供给使用者的,FileStream 会在提供的路径中创建文件:

    await using FileStream fs = new(path, FileMode.Create);
    await browserFile.OpenReadStream().CopyToAsync(fs);
    

    支持:建议对 Microsoft Azure Blob 存储使用以下方法,因为文件的 Stream 是直接提供给 UploadBlobAsync 的:

    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.OpenReadStreamStreamReader.ReadAsync 时使用 CancellationToken 检测用户何时取消上传。

    InputFile 组件创建 CancellationTokenSource。 在 OnInputFileChange 方法开始时,检查以前的上传是否正在进行。

    如果文件上传正在进行:

  • 在上次上传时调用 Cancel
  • 为下次上传创建新的 CancellationTokenSource,然后将 CancellationTokenSource.Token 传递到 OpenReadStreamReadAsync
  • 上传文件,显示进度

    下面的示例展示了如何在 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 inputimg 元素调用的函数,该函数执行以下操作:

  • 提取所选文件。
  • 使用 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:
  •