From d282ce6ea3c64b2f19a267e60083a0f09523ff61 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:24:40 -0600 Subject: [PATCH] Progress For Blazor File Upload (#24172) --- aspnetcore/blazor/file-uploads.md | 46 ++++++++ .../Pages/file-uploads/FileUpload3.razor | 102 ++++++++++++++++++ .../Pages/file-uploads/FileUpload3.razor | 102 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 aspnetcore/blazor/samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor create mode 100644 aspnetcore/blazor/samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 54fbea2feb..596db7b7dd 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -361,6 +361,29 @@ public class FilesaveController : ControllerBase In the preceding code, is called to generate a secure filename. Never trust the filename provided by the browser, as an attacker may choose an existing filename that overwrites an existing file or send a path that attempts to write outside of the app. +::: zone pivot="server" + +## Upload files with progress + +The following example demonstrates how to upload files in a Blazor Server 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 MB 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. + +`Pages/FileUpload3.razor`: + +[!code-razor[](~/blazor/samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor)] + +For more information, see the following API resources: + +* : Provides a for a file, supporting both synchronous and asynchronous read and write operations. +* : The preceding `FileUpload3` component reads the stream asynchronously with . Reading a stream synchronously with isn't supported in Razor components. + +::: zone-end + ## File streams ::: zone pivot="webassembly" @@ -729,6 +752,29 @@ public class FilesaveController : ControllerBase } ``` +::: zone pivot="server" + +## Upload files with progress + +The following example demonstrates how to upload files in a Blazor Server 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 MB 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. + +`Pages/FileUpload3.razor`: + +[!code-razor[](~/blazor/samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor)] + +For more information, see the following API resources: + +* : Provides a for a file, supporting both synchronous and asynchronous read and write operations. +* : The preceding `FileUpload3` component reads the stream asynchronously with . Reading a stream synchronously with isn't supported in Razor components. + +::: zone-end + ## File streams ::: zone pivot="webassembly" diff --git a/aspnetcore/blazor/samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor b/aspnetcore/blazor/samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor new file mode 100644 index 0000000000..ed7c7a4cb9 --- /dev/null +++ b/aspnetcore/blazor/samples/5.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor @@ -0,0 +1,102 @@ +@page "/file-upload-3" +@using System +@using System.IO +@using Microsoft.AspNetCore.Hosting +@using Microsoft.Extensions.Logging +@inject ILogger Logger +@inject IWebHostEnvironment Environment + +

Upload Files

+ +

+ +

+ +

+ +

+ +

+ +

+ +@if (isLoading) +{ +

Progress: @string.Format("{0:P0}", progressPercent)

+} +else +{ +
    + @foreach (var file in loadedFiles) + { +
  • +
      +
    • Name: @file.Name
    • +
    • Last modified: @file.LastModified.ToString()
    • +
    • Size (bytes): @file.Size
    • +
    • Content type: @file.ContentType
    • +
    +
  • + } +
+} + +@code { + private List loadedFiles = new(); + private long maxFileSize = 1024 * 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)) + { + try + { + 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; + } +} diff --git a/aspnetcore/blazor/samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor b/aspnetcore/blazor/samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor new file mode 100644 index 0000000000..ed7c7a4cb9 --- /dev/null +++ b/aspnetcore/blazor/samples/6.0/BlazorSample_Server/Pages/file-uploads/FileUpload3.razor @@ -0,0 +1,102 @@ +@page "/file-upload-3" +@using System +@using System.IO +@using Microsoft.AspNetCore.Hosting +@using Microsoft.Extensions.Logging +@inject ILogger Logger +@inject IWebHostEnvironment Environment + +

Upload Files

+ +

+ +

+ +

+ +

+ +

+ +

+ +@if (isLoading) +{ +

Progress: @string.Format("{0:P0}", progressPercent)

+} +else +{ +
    + @foreach (var file in loadedFiles) + { +
  • +
      +
    • Name: @file.Name
    • +
    • Last modified: @file.LastModified.ToString()
    • +
    • Size (bytes): @file.Size
    • +
    • Content type: @file.ContentType
    • +
    +
  • + } +
+} + +@code { + private List loadedFiles = new(); + private long maxFileSize = 1024 * 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)) + { + try + { + 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; + } +}