From 8e65894b53c9a436e08b1cea8cf92ede5f387bb0 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:10:05 -0500 Subject: [PATCH 01/10] File upload article enhancements --- aspnetcore/blazor/file-uploads.md | 125 +++++++++++++++++- .../_static/default-thumbnail.jpg | Bin 0 -> 19711 bytes 2 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 aspnetcore/blazor/file-uploads/_static/default-thumbnail.jpg diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 98edb37d3620..1c12d8983ad8 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -45,43 +45,53 @@ Rendered HTML: > [!NOTE] > In the preceding example, the `` element's `_bl_2` attribute is used for Blazor's internal processing. -To read data from a user-selected file, call on the file and read from the returned stream. For more information, see the [File streams](#file-streams) section. +To read data from a user-selected file from a that represents the file's bytes, call on the file and read from the returned stream. For more information, see the [File streams](#file-streams) section. enforces a maximum size in bytes of its . Reading one file or multiple files larger than 500 KB results in an exception. This limit prevents developers from accidentally reading large files into memory. The `maxAllowedSize` parameter of can be used to specify a larger size if required. -If you need access to a that represents the file's bytes, use . Avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: +For scenarios other than processing a small file, such as saving a thumbnail or avatar to a database, avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: * Copy the stream directly to a file on disk without reading it into memory. Note that Blazor apps executing code on the server aren't able to access the client's file system directly. * Upload files from the client directly to an external service. For more information, see the [Upload files to an external service](#upload-files-to-an-external-service) section. -In the following examples, `browserFile` represents the uploaded file and implements . Working implementations for are shown in the file upload components later in this article. +In the following examples, `browserFile` implements to represent an uploaded file. Working implementations for are shown in the file upload components later in this article. + +When calling , we recommend passing a maximum allowed file size in `maxAllowedSize` at the limit of the file sizes that you expect, which is the maximum number of bytes that can be supplied by the with a default value of 500 KB. The examples in this article use a local variable or constant assignment (usually not shown) named `maxFileSize`. Supported: The following approach is **recommended** because the file's is provided directly to the consumer, a that creates the file at the provided path: ```csharp await using FileStream fs = new(path, FileMode.Create); -await browserFile.OpenReadStream().CopyToAsync(fs); +await browserFile.OpenReadStream(maxFileSize).CopyToAsync(fs); ``` Supported: The following approach is **recommended** for [Microsoft Azure Blob Storage](/azure/storage/blobs/storage-blobs-overview) because the file's is provided directly to : ```csharp await blobContainerClient.UploadBlobAsync( - trustedFileName, browserFile.OpenReadStream()); + trustedFileName, browserFile.OpenReadStream(maxFileSize)); +``` + +Only recommended for small files: The following approach is only **recommended for small files** because the file's content is read into a in memory (`memoryStream`), which incurs a performance penalty and [DoS](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk. For an example that demonstrates this technique to save a thumbnail image with an to a database using [Entity Framework Core (EF Core)](/ef/core/), see the [Save small files directly to a database with EF Core](#save-small-files-directly-to-a-database-with-ef-core) section later in this article. + +```csharp +using var memoryStream = new MemoryStream(); +await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream); +var smallFileByteArray = memoryStream.ToArray(); ``` Not recommended: The following approach is **NOT recommended** because the file's content is read into a in memory (`reader`): ```csharp var reader = - await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync(); + await new StreamReader(browserFile.OpenReadStream(maxFileSize)).ReadToEndAsync(); ``` Not recommended: The following approach is **NOT recommended** for [Microsoft Azure Blob Storage](/azure/storage/blobs/storage-blobs-overview) because the file's content is copied into a in memory (`memoryStream`) before calling : ```csharp var memoryStream = new MemoryStream(); -await browserFile.OpenReadStream().CopyToAsync(memoryStream); +await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream); await blobContainerClient.UploadBlobAsync( trustedFileName, memoryStream)); ``` @@ -873,6 +883,107 @@ The following `FileUpload4` component shows the complete example. :::moniker-end +## Save small files directly to a database with EF Core + +Many ASP.NET Core apps use [Entity Framework Core (EF Core)](/ef/core/) to manage database operations. Saving thumbnails and avatars directly to the database is a common requirement. This section demonstrates a general approach that can be further enhanced for production apps. + +The following pattern: + +* Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). +* Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). + +For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` file change. Blazor Server app components are always interactive and don't require a render mode. + +In the following example, a small thumbnail (<= 100 KB) in an is saved to a database with EF Core. If a file isn't selected by the user for the `InputFile` component, a default thumbnail is saved to the database. + +The default thumbnail (`default-thumbnail.jpg`) is at the project root with a **Copy to Output Directory** setting of **Copy if newer**: + +![Default generic thumbnail image](~/blazor/file-uploads/_static/default-thumbnail.jpg) + +The `Movie` model (`Movie.cs`) has a byte array property, named `Thumbnail`, to hold the thumbnail image's bytes: + +```csharp +public byte[]? Thumbnail { get; set; } +``` + +Components that display the thumbnail pass image data to the `img` tag's `src` attribute as JPEG, base-64 encoded data: + +```razor +User thumbnail +``` + +In the following `Create` component, an image upload is processed. You can enhance the example further with custom validation for file type and size using the approaches in . To see the full `Create` component without the thumbnail upload code in the following example, see the `BlazorWebAppMovies` sample app in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). + +`Components/Pages/MoviePages/Create.razor`: + +```razor +@page "/movies/create" +@rendermode InteractiveServer +@using Microsoft.EntityFrameworkCore +@using BlazorWebAppMovies.Models +@inject IDbContextFactory DbFactory +@inject NavigationManager NavigationManager + +... + +
+
+ + + + + ... + +
+ + +
+ +
+
+
+ +... + +@code { + private const long maxFileSize = 102400; + private IBrowserFile? browserFile; + + [SupplyParameterFromForm] + private Movie Movie { get; set; } = new(); + + private void HandleSelectedThumbnail(InputFileChangeEventArgs e) + { + browserFile = e.File; + } + + private async Task AddMovie() + { + using var context = DbFactory.CreateDbContext(); + + if (browserFile?.Size > 0 && browserFile?.Size <= maxFileSize) + { + using var memoryStream = new MemoryStream(); + await browserFile!.OpenReadStream(maxFileSize).CopyToAsync(memoryStream); + + Movie!.Thumbnail = memoryStream.ToArray(); + } + else + { + Movie!.Thumbnail = File.ReadAllBytes( + $"{AppDomain.CurrentDomain.BaseDirectory}default_thumbnail.jpg"); + } + + context.Movie.Add(Movie); + await context.SaveChangesAsync(); + NavigationManager.NavigateTo("/movies"); + } +} +``` + +The same approach would be adopted in the `Edit` component with an interactive render mode if users were allowed to edit a movie's thumbnail image. + ## Upload files to an external service Instead of an app handling file upload bytes and the app's server receiving uploaded files, clients can directly upload files to an external service. The app can safely process the files from the external service on demand. This approach hardens the app and its server against malicious attacks and potential performance problems. diff --git a/aspnetcore/blazor/file-uploads/_static/default-thumbnail.jpg b/aspnetcore/blazor/file-uploads/_static/default-thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..097416034c9e775fd657d029ce9abcb2fedf72ef GIT binary patch literal 19711 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 Date: Tue, 11 Feb 2025 15:15:33 -0500 Subject: [PATCH 02/10] Updates --- aspnetcore/blazor/tutorials/movie-database-app/part-8.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-8.md b/aspnetcore/blazor/tutorials/movie-database-app/part-8.md index f3423ed01670..18ba82a29f91 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-8.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-8.md @@ -285,6 +285,8 @@ If you're new to Blazor, we recommend reading the following Blazor articles that * * covers concurrency with EF Core in Blazor apps. +For guidance on adding a thumbnail file upload feature to this tutorial's sample app, see . + In the documentation website's sidebar navigation, articles are organized by subject matter and laid out in roughly in a general-to-specific or basic-to-complex order. The best approach when starting to learn about Blazor is to read down the table of contents from top to bottom. ## Troubleshoot with the completed sample From b0da568e382348a29be1777b30219feebd97f851 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:23:28 -0500 Subject: [PATCH 03/10] Updates --- aspnetcore/blazor/file-uploads.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 1c12d8983ad8..f583c1e313cc 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -889,8 +889,10 @@ Many ASP.NET Core apps use [Entity Framework Core (EF Core)](/ef/core/) to manag The following pattern: + + * Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). -* Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). +* Can be enhanced with additional code for file size and content type [validation feedback](/aspnet/core/blazor/forms/validation). For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` file change. Blazor Server app components are always interactive and don't require a render mode. From 6142ef19c02bc700c4b658136d812c641c236700 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:59:31 -0500 Subject: [PATCH 04/10] Updates --- aspnetcore/blazor/file-uploads.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index f583c1e313cc..1cdcb865f0ce 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -889,10 +889,8 @@ Many ASP.NET Core apps use [Entity Framework Core (EF Core)](/ef/core/) to manag The following pattern: - - * Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). -* Can be enhanced with additional code for file size and content type [validation feedback](/aspnet/core/blazor/forms/validation). +* Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` file change. Blazor Server app components are always interactive and don't require a render mode. @@ -915,7 +913,7 @@ Components that display the thumbnail pass image data to the `img` tag's `src` a alt="User thumbnail" /> ``` -In the following `Create` component, an image upload is processed. You can enhance the example further with custom validation for file type and size using the approaches in . To see the full `Create` component without the thumbnail upload code in the following example, see the `BlazorWebAppMovies` sample app in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). +In the following `Create` component, an image upload is processed. You can enhance the example further with custom validation for file type and size using the approaches in . To see the full `Create` component without the thumbnail upload code in the following example, see the `BlazorWebAppMovies` sample app in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). `Components/Pages/MoviePages/Create.razor`: From 217c723d8f074f39292a7784975c45ade7b727da Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 08:15:55 -0500 Subject: [PATCH 05/10] Updates --- aspnetcore/blazor/file-uploads.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 1cdcb865f0ce..be5d4aa7416f 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -45,18 +45,18 @@ Rendered HTML: > [!NOTE] > In the preceding example, the `` element's `_bl_2` attribute is used for Blazor's internal processing. -To read data from a user-selected file from a that represents the file's bytes, call on the file and read from the returned stream. For more information, see the [File streams](#file-streams) section. +To read data from a user-selected file with a that represents the file's bytes, call on the file and read from the returned stream. For more information, see the [File streams](#file-streams) section. enforces a maximum size in bytes of its . Reading one file or multiple files larger than 500 KB results in an exception. This limit prevents developers from accidentally reading large files into memory. The `maxAllowedSize` parameter of can be used to specify a larger size if required. -For scenarios other than processing a small file, such as saving a thumbnail or avatar to a database, avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: +For scenarios other than processing a small file, such as saving a small image to a database, avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: * Copy the stream directly to a file on disk without reading it into memory. Note that Blazor apps executing code on the server aren't able to access the client's file system directly. * Upload files from the client directly to an external service. For more information, see the [Upload files to an external service](#upload-files-to-an-external-service) section. In the following examples, `browserFile` implements to represent an uploaded file. Working implementations for are shown in the file upload components later in this article. -When calling , we recommend passing a maximum allowed file size in `maxAllowedSize` at the limit of the file sizes that you expect, which is the maximum number of bytes that can be supplied by the with a default value of 500 KB. The examples in this article use a local variable or constant assignment (usually not shown) named `maxFileSize`. +When calling , we recommend passing a maximum allowed file size in the `maxAllowedSize` parameter at the limit of the file sizes that you expect to receive. The default value is 500 KB. This article's examples use a maximum allowed file size variable or constant named `maxFileSize` but usually don't show setting a specific value. Supported: The following approach is **recommended** because the file's is provided directly to the consumer, a that creates the file at the provided path: @@ -892,7 +892,7 @@ The following pattern: * Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). * Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). -For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` file change. Blazor Server app components are always interactive and don't require a render mode. +For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` component file change (`OnChange` parameter/event). Blazor Server app components are always interactive and don't require a render mode. In the following example, a small thumbnail (<= 100 KB) in an is saved to a database with EF Core. If a file isn't selected by the user for the `InputFile` component, a default thumbnail is saved to the database. @@ -900,7 +900,7 @@ The default thumbnail (`default-thumbnail.jpg`) is at the project root with a ** ![Default generic thumbnail image](~/blazor/file-uploads/_static/default-thumbnail.jpg) -The `Movie` model (`Movie.cs`) has a byte array property, named `Thumbnail`, to hold the thumbnail image's bytes: +The `Movie` model (`Movie.cs`) has a byte array property (`Thumbnail`) to hold the thumbnail image's bytes: ```csharp public byte[]? Thumbnail { get; set; } @@ -929,7 +929,8 @@ In the following `Create` component, an image upload is processed. You can enhan
- + @@ -937,7 +938,8 @@ In the following `Create` component, an image upload is processed. You can enhan
- +
From f2c95dca0ae49ef4cf8dba4bcf60cc105e392277 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:04:16 -0500 Subject: [PATCH 06/10] Updates --- aspnetcore/blazor/file-uploads.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index be5d4aa7416f..892d9383f71c 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -49,7 +49,7 @@ To read data from a user-selected file with a that repre enforces a maximum size in bytes of its . Reading one file or multiple files larger than 500 KB results in an exception. This limit prevents developers from accidentally reading large files into memory. The `maxAllowedSize` parameter of can be used to specify a larger size if required. -For scenarios other than processing a small file, such as saving a small image to a database, avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: +Outside of processing a small file, avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a or read the entire stream into a byte array all at once. These approaches can result in degraded app performance and potential [Denial of Service (DoS)](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk, especially for server-side components. Instead, consider adopting either of the following approaches: * Copy the stream directly to a file on disk without reading it into memory. Note that Blazor apps executing code on the server aren't able to access the client's file system directly. * Upload files from the client directly to an external service. For more information, see the [Upload files to an external service](#upload-files-to-an-external-service) section. @@ -891,6 +891,7 @@ The following pattern: * Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). * Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). +* Incurs a performance penalty and [DoS](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk. Carefully weigh the risk when reading any file into memory and consider alternative approaches, especially for larger files. Alternative approaches include saving files directly to disk or a third-party service for post-processing and serving to clients. For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` component file change (`OnChange` parameter/event). Blazor Server app components are always interactive and don't require a render mode. From d9be9d88703c446d69496bed3b236b569f91f6ac Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:09:26 -0500 Subject: [PATCH 07/10] Updates --- aspnetcore/blazor/file-uploads.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 892d9383f71c..20596e4080d4 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -891,7 +891,7 @@ The following pattern: * Is based on the [Blazor movie database tutorial app](xref:blazor/tutorials/movie-database-app/index). * Can be enhanced with additional code for file size and content type [validation feedback](xref:blazor/forms/validation). -* Incurs a performance penalty and [DoS](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk. Carefully weigh the risk when reading any file into memory and consider alternative approaches, especially for larger files. Alternative approaches include saving files directly to disk or a third-party service for post-processing and serving to clients. +* Incurs a performance penalty and [DoS](xref:blazor/security/interactive-server-side-rendering#denial-of-service-dos-attacks) risk. Carefully weigh the risk when reading any file into memory and consider alternative approaches, especially for larger files. Alternative approaches include saving files directly to disk or a third-party service for antivirus/antimalware checks, further processing, and serving to clients. For the following example to work in a Blazor Web App (ASP.NET Core 8.0 or later), the component must adopt an [interactive render mode](xref:blazor/fundamentals/index#static-and-interactive-rendering-concepts) (for example, `@rendermode InteractiveServer`) to call `HandleSelectedThumbnail` on an `InputFile` component file change (`OnChange` parameter/event). Blazor Server app components are always interactive and don't require a render mode. From 2ed164ab87b99f6d3fd2e25398a44185ee201a2a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:44:20 -0500 Subject: [PATCH 08/10] Updates --- aspnetcore/blazor/file-uploads.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 20596e4080d4..b6d61ca292d3 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -901,12 +901,14 @@ The default thumbnail (`default-thumbnail.jpg`) is at the project root with a ** ![Default generic thumbnail image](~/blazor/file-uploads/_static/default-thumbnail.jpg) -The `Movie` model (`Movie.cs`) has a byte array property (`Thumbnail`) to hold the thumbnail image's bytes: +The `Movie` model (`Movie.cs`) has a property (`Thumbnail`) to hold the thumbnail image data: ```csharp public byte[]? Thumbnail { get; set; } ``` +Image data is stored in a byte array (`byte[]`) in the database. The app base-64 encodes the bytes for display because base-64 encoded data is roughly a third larger than the raw bytes of the image, thus base-64 image data requires additional database storage and reduces the performance of database read/write operations. + Components that display the thumbnail pass image data to the `img` tag's `src` attribute as JPEG, base-64 encoded data: ```razor From c66b6f01db6f6760dcb4840f8752a317153816ed Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:00:43 -0500 Subject: [PATCH 09/10] Updates --- aspnetcore/blazor/file-uploads.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index b6d61ca292d3..c561265e1bfa 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -904,10 +904,11 @@ The default thumbnail (`default-thumbnail.jpg`) is at the project root with a ** The `Movie` model (`Movie.cs`) has a property (`Thumbnail`) to hold the thumbnail image data: ```csharp +[Column(TypeName = "varbinary(MAX)")] public byte[]? Thumbnail { get; set; } ``` -Image data is stored in a byte array (`byte[]`) in the database. The app base-64 encodes the bytes for display because base-64 encoded data is roughly a third larger than the raw bytes of the image, thus base-64 image data requires additional database storage and reduces the performance of database read/write operations. +Image data is stored as bytes in the database as [`varbinary(MAX)`](/sql/t-sql/data-types/binary-and-varbinary-transact-sql). The app base-64 encodes the bytes for display because base-64 encoded data is roughly a third larger than the raw bytes of the image, thus base-64 image data requires additional database storage and reduces the performance of database read/write operations. Components that display the thumbnail pass image data to the `img` tag's `src` attribute as JPEG, base-64 encoded data: From 213b6721c180c7ae937c69d1665da43aae98937d Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:03:22 -0500 Subject: [PATCH 10/10] Updates --- aspnetcore/blazor/file-uploads.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index c561265e1bfa..289ca58251f5 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -971,13 +971,13 @@ In the following `Create` component, an image upload is processed. You can enhan if (browserFile?.Size > 0 && browserFile?.Size <= maxFileSize) { using var memoryStream = new MemoryStream(); - await browserFile!.OpenReadStream(maxFileSize).CopyToAsync(memoryStream); + await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream); - Movie!.Thumbnail = memoryStream.ToArray(); + Movie.Thumbnail = memoryStream.ToArray(); } else { - Movie!.Thumbnail = File.ReadAllBytes( + Movie.Thumbnail = File.ReadAllBytes( $"{AppDomain.CurrentDomain.BaseDirectory}default_thumbnail.jpg"); }