ASP.NET Core Blazor-Dateiuploads
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
In diesem Artikel wird erläutert, wie Sie Dateien in Blazor mithilfe der InputFile-Komponente hochladen.
Dateiuploads
Warnung
Befolgen Sie immer die bewährten Methoden für die Sicherheit, wenn Sie Benutzern das Hochladen von Dateien erlauben. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
Verwenden Sie die InputFile-Komponente, um Browserdateidaten in .NET-Code zu lesen. Die InputFile-Komponente rendert ein HTML-<input>
-Element des Typs file
für einzelne Datei-Uploads. Fügen Sie das multiple
-Attribut hinzu, um Benutzern das gleichzeitige Hochladen mehrerer Dateien zu ermöglichen.
Die Dateiauswahl ist beim Verwenden einer InputFile-Komponente oder der zugrunde liegenden HTML-<input type="file">
nicht kumulativ, sodass Sie einer vorhandenen Dateiauswahl keine Dateien hinzufügen können. Die Komponente ersetzt immer die anfängliche Dateiauswahl der Benutzer*innen, sodass Dateiverweise aus früheren Auswahlen nicht verfügbar sind.
Die folgende InputFile-Komponente führt die Methode LoadFiles
aus, wenn das Ereignis OnChange (change
) auftritt. InputFileChangeEventArgs bietet Zugriff auf die ausgewählte Dateiliste und Informationen zu den einzelnen Dateien:
<InputFile OnChange="LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
Gerenderter HTML-Code:
<input multiple="" type="file" _bl_2="">
Hinweis
Im obigen Beispiel wird das _bl_2
-Attribut des <input>
-Elements für die interne Verarbeitung von Blazor verwendet.
Um Daten aus einer vom Benutzer ausgewählten Datei zu lesen, rufen Sie IBrowserFile.OpenReadStream für die Datei auf und lesen aus dem zurückgegebenen Stream. Weitere Informationen finden Sie im Abschnitt Dateistreams.
OpenReadStream erzwingt eine maximale Größe in Bytes für die Stream-Klasse. Das Lesen einer oder mehrerer Dateien, die größer als 500 KB sind, führt zu einer Ausnahme. Dieser Grenzwert soll Entwickler daran hindern, versehentlich zu große Dateien in den Arbeitsspeicher einzulesen. Der Parameter maxAllowedSize
von OpenReadStream kann bei Bedarf zum Festlegen einer größeren Größe verwendet werden.
Verwenden Sie IBrowserFile.OpenReadStream, wenn Sie auf eine Stream-Klasse zugreifen müssen, die die Bytes der Datei darstellt. Vermeiden Sie es, den eingehenden Dateistrom auf einmal direkt in den Arbeitsspeicher einzulesen. Kopieren Sie beispielsweise nicht alle Bytes der Datei in MemoryStream, oder lesen Sie nicht den gesamten Datenstrom auf einmal in ein Bytearray ein. Diese Ansätze können zu beeinträchtigter App-Leistung und potenziellem Denial of Service (DoS) -Risiko führen, insbesondere für serverseitige Komponenten. Erwägen Sie stattdessen einen der folgenden Ansätze:
- Kopieren Sie den Datenstrom direkt in eine Datei auf dem Datenträger, ohne ihn in den Arbeitsspeicher einlesen zu müssen. Beachten Sie, dass Blazor-Apps, die Code auf dem Server ausführen, nicht direkt auf das Dateisystem des Clients zugreifen können.
- Laden Sie Dateien vom Client direkt in einen externen Dienst hoch. Weitere Informationen finden Sie im Abschnitt Hochladen von Dateien in einen externen Dienst.
In den folgenden Beispielen stellt browserFile
die hochgeladene Datei dar und implementiert IBrowserFile. Funktionierende Implementierungen für IBrowserFile werden in den Komponenten zum Dateiupload weiter unten in diesem Artikel gezeigt.
Unterstützt: Der folgende Ansatz wird empfohlen, weil die Stream-Klasse der Datei dem Consumer direkt bereitgestellt wird und eine FileStream-Klasse die Datei unter dem angegebenen Pfad erstellt:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);
Unterstützt: Der folgende Ansatz wird für Microsoft Azure Blob Storageempfohlen, da die Stream-Klasse der Datei direkt für UploadBlobAsync bereitgestellt wird:
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
Nicht empfohlen: Der folgende Ansatz wird NICHT empfohlen, da der Inhalt der Datei Stream in einen String im Arbeitsspeicher (reader
) eingelesen wird:
var reader =
await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();
Nicht empfohlen: Der folgende Ansatz wird für Microsoft Azure Blob Storage NICHT empfohlen, da der Inhalt der Datei Stream in einen MemoryStream im Arbeitsspeicher (memoryStream
) kopiert wird, bevor UploadBlobAsync aufgerufen wird:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
Eine Komponente, die eine Bilddatei empfängt, kann die BrowserFileExtensions.RequestImageFileAsync-Hilfsmethode für die Datei aufrufen, um die Größe der Bilddaten in der JavaScript-Laufzeit des Browsers zu ändern, bevor das Bild in die App gestreamt wird. Anwendungsfälle für das Aufrufen von RequestImageFileAsync eignen sich am besten für Blazor WebAssembly-Apps.
Benutzende von Autofac Inversion of Control (IoC)-Containern
Wenn Sie den Autofac IoC-Container (Inversion of Control) anstelle des integrierten ASP.NET Core-Abhängigkeitsinjektionscontainers verwenden, legen Sie DisableImplicitFromServicesParameters in den serverseitigen Circuit-Handler-Hub-Optionen auf true
fest. Weitere Informationen finden Sie unter FileUpload: Es wurden innerhalb der vorgegebenen Zeit keine Daten empfangen (dotnet/aspnetcore
#38842).
Lese- und Uploadgrenzwerte für die Dateigröße
Bei serverseitigen oder clientseitigen Apps gibt es speziell für die Komponente InputFile keine Größenbeschränkung für das Lesen oder Hochladen von Dateien. Blazor auf der Clientseite liest jedoch die Bytes der Datei in einen einzelnen JavaScript-Arraypuffer, wenn die Daten von JavaScript nach C# gemarshallt werden. Dieser Vorgang ist auf 2 GB oder den verfügbaren Arbeitsspeicher des Geräts beschränkt. Große Dateiuploads (> 250 MB) können bei clientseitigen Uploads mit der InputFile-Komponente fehlschlagen. Weitere Informationen finden Sie in den folgenden Diskussionen:
Die maximale unterstützte Dateigröße für die InputFile-Komponente beträgt 2 GB. Darüber hinaus liest Blazor auf der Clientseite die Bytes der Datei in einen einzelnen JavaScript-Arraypuffer, wenn die Daten von JavaScript nach C# gemarshallt werden. Dieser Vorgang ist auf 2 GB oder den verfügbaren Arbeitsspeicher des Geräts beschränkt. Große Dateiuploads (> 250 MB) können bei clientseitigen Uploads mit der InputFile-Komponente fehlschlagen. Weitere Informationen finden Sie in den folgenden Diskussionen:
- The Blazor InputFile Component should handle chunking when the file is uploaded (dotnet/runtime #84685)
- Request Streaming upload via http handler (dotnet/runtime #36634)
Bei großen clientseitigen Dateiuploads, die beim Versuch der Verwendung der InputFile-Komponente fehlschlägt, wird empfohlen, große Dateien mit einer benutzerdefinierten Komponente mithilfe mehrerer HTTP-Bereichsanforderungen in mehrere Blöcke zu unterteilen, anstatt die InputFile-Komponente zu verwenden.
Derzeit ist für .NET 9 (Ende 2024) geplant, die clientseitige Beschränkung der Dateigröße beim Upload zu behandeln.
Beispiele
Die folgenden Beispiele veranschaulichen das Hochladen mehrerer Dateien in eine Komponente. InputFileChangeEventArgs.GetMultipleFiles ermöglicht das Lesen mehrerer Dateien. Legen Sie die maximale Anzahl von Dateien fest, um zu verhindern, dass ein böswilliger Benutzer eine größere Anzahl von Dateien hochlädt als die App erwartet. InputFileChangeEventArgs.File ermöglicht das Lesen der ersten und einzigen Datei, wenn das Hochladen mehrerer Dateien nicht unterstützt wird.
InputFileChangeEventArgs befindet sich im Microsoft.AspNetCore.Components.Forms-Namespace, der in der Regel einer der Namespaces in der _Imports.razor
-Datei der App ist. Wenn der Namespace in der _Imports.razor
-Datei vorhanden ist, bietet dieser den Komponenten der App API-Memberzugriff:
Namespaces in der _Imports.razor
-Datei werden nicht auf C#-Dateien angewendet (.cs
). C#-Dateien erfordern eine explizite using
-Anweisung am Anfang der Klassendatei:
using Microsoft.AspNetCore.Components.Forms;
Zum Testen von Dateiuploadkomponenten können Sie Testdateien beliebiger Größe mit PowerShell erstellen:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
Für den obigen Befehl gilt Folgendes:
- Der Platzhalter
{SIZE}
ist die Größe der Datei in Bytes (z. B.2097152
für eine 2 MB große Datei). - Der Platzhalter
{PATH}
ist der Pfad und die Datei mit der Dateierweiterung (z. B.D:/test_files/testfile2MB.txt
).
Beispiel für den serverseitigen Dateiupload
Erstellen Sie den Ordner Development/unsafe_uploads
im Stammverzeichnis der App, die in der Development
-Umgebung ausgeführt wird, um den folgenden Code zu verwenden.
Da im Beispiel die Umgebung der App als Teil des Pfads verwendet wird, unter dem Dateien gespeichert werden, sind zusätzliche Ordner erforderlich, wenn andere Umgebungen in Test- und Produktionsumgebungen verwendet werden. Erstellen Sie beispielsweise einen Staging/unsafe_uploads
-Ordner für die Staging
-Umgebung. Erstellen Sie einen Production/unsafe_uploads
-Ordner für die Production
-Umgebung.
Warnung
Im Beispiel werden Dateien gespeichert, ohne ihre Inhalte zu überprüfen, und in den Anleitungen dieses Artikels werden keine zusätzlichen bewährten Sicherheitsmethoden für hochgeladene Dateien berücksichtigt. Deaktivieren Sie bei Staging- und Produktionssystemen die Ausführungsberechtigung für den Uploadordner, und prüfen Sie Dateien unmittelbar nach dem Upload mit einer Antiviren-/Antischadsoftwarescanner-API. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
FileUpload1.razor
:
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<PageTitle>File Upload 1</PageTitle>
<h1>File Upload Example 1</h1>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileName);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
loadedFiles.Add(file);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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 System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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 System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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;
}
}
Beispiel für den clientseitigen Dateiupload
Im folgenden Beispiel werden Dateibytes verarbeitet und keine Dateien an ein Ziel außerhalb der App gesendet. Ein Beispiel für eine Razor-Komponente, die eine Datei an einen Server oder Dienst sendet, finden Sie in den folgenden Abschnitten:
- Dateien auf einen Server mit clientseitigem Rendering (CSR) hochladen
- Hochladen von Dateien in einen externen Dienst
Die Komponente geht davon aus, dass der interaktive WebAssembly-Rendermodus (InteractiveWebAssembly
) von einer übergeordneten Komponente geerbt oder global auf die App angewendet wird.
@page "/file-upload-1"
@inject ILogger<FileUpload1> Logger
<PageTitle>File Upload 1</PageTitle>
<h1>File Upload Example 1</h1>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
IBrowserFile gibt Metadaten zurück, die vom Browser als Eigenschaften verfügbar gemacht werden. Verwenden Sie diese Metadaten für die vorläufige Überprüfung.
Vertrauen Sie nie den Werten der vorangehenden Eigenschaften, insbesondere nicht denen der Name-Eigenschaft für die Anzeige auf der Benutzeroberfläche. Behandeln Sie alle von Benutzern bereitgestellten Daten als erhebliches Sicherheitsrisiko für die App, den Server und das Netzwerk. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
Hochladen von Dateien auf einen Server mit serverseitigem Rendering
Dieser Abschnitt gilt für Interactive Server-Komponenten in Blazor Web Apps oder Blazor ServerAnwendungen.
Im folgenden Beispiel wird das Hochladen von Dateien aus einer serverseitigen App in einen Back-End-Web-API-Controller in einer separaten App veranschaulicht, die sich möglicherweise auf einem separaten Server befindet.
Fügen Sie in der Datei Program
der serverseitigen App IHttpClientFactory und die zugehörigen Dienste hinzu, die der App das Erstellen von HttpClient-Instanzen ermöglichen:
builder.Services.AddHttpClient();
Weitere Informationen erhalten Sie unter Stellen von HTTP-Anforderungen mithilfe von IHttpClientFactory in ASP.NET Core.
Für die Beispiele in diesem Abschnitt gilt Folgendes:
- Die Web-API wird unter der folgenden URL ausgeführt:
https://localhost:5001
- Die serverseitige App wird unter der folgenden URL ausgeführt:
https://localhost:5003
Die oben genannten URLs wurden zu Testzwecken in den Properties/launchSettings.json
-Dateien des Projekts konfiguriert.
Die folgende UploadResult
-Klasse verwaltet das Ergebnis einer hochgeladenen Datei. Wenn eine Datei nicht auf den Server hochgeladen werden kann, wird in ErrorCode
ein Fehlercode zurückgegeben, der dem Benutzer angezeigt werden kann. Auf dem Server wird für jede Datei ein sicherer Dateiname generiert und zur Anzeige an den Client in StoredFileName
zurückgegeben. Dateien werden zwischen dem Client und dem Server mit dem unsicheren/nicht vertrauenswürdigen Dateinamen in FileName
verschlüsselt.
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; }
}
Eine bewährte Sicherheitsmethode für Produktions-Apps besteht darin, das Senden von Fehlermeldungen an Clients zu vermeiden, die vertrauliche Informationen über eine App, einen Server oder ein Netzwerk offenlegen könnten. Die Bereitstellung ausführlicher Fehlermeldungen kann einen böswilligen Benutzer bei der Entwicklung von Angriffen auf eine App, einen Server oder ein Netzwerk unterstützen. Der Beispielcode in diesem Abschnitt sendet nur eine Fehlercodenummer (int
) zur Anzeige durch die clientseitige Komponente, wenn ein serverseitiger Fehler auftritt. Wenn ein Benutzer Hilfe bei einem Dateiupload benötigt, gibt dieser den Fehlercode zur Lösung eines Supporttickets an das Supportpersonal weiter, ohne jemals selbst etwas über die genaue Ursache des Fehlers zu erhalten.
Die folgende LazyBrowserFileStream
Klasse definiert einen benutzerdefinierten Stream-Typ, der OpenReadStream erst aufgerufen wird, kurz bevor die ersten Bytes des Streams angefordert werden. Der Stream wird erst vom Browser an den Server übertragen, wenn das Lesen des Streams in .NET beginnt.
LazyBrowserFileStream.cs
:
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
: Stream
{
private readonly IBrowserFile file = file;
private readonly int maxAllowedSize = maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public override void Flush() => underlyingStream?.Flush();
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen() =>
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
Die folgende FileUpload2
-Komponente:
- Ermöglicht Benutzern das Hochladen von Dateien vom Client.
- zeigt den nicht vertrauenswürdigen bzw. unsicheren Dateinamen an, der vom Client auf der Benutzeroberfläche angegeben wird. Der nicht vertrauenswürdige bzw. unsichere Dateiname wird automatisch von Razor HTML-codiert, um die sichere Anzeige auf der Benutzeroberfläche zu ermöglichen.
Warnung
Vertrauen Sie von Clients bereitgestellte Dateinamen für folgende Zwecke nicht:
- Speichern der Datei in einem Dateisystem oder Dienst
- Anzeigen auf Benutzeroberflächen, die Dateinamen weder automatisch oder über Entwicklercode codieren
Weitere Informationen zu Sicherheitsüberlegungen beim Hochladen von Dateien auf einen Server finden Sie unter Hochladen von Dateien in ASP.NET Core.
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Any())
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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;
int 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)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
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; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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;
int 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)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
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; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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;
int 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)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
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; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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;
int 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)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
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; }
}
}
Wenn die Komponente das Hochladen von Dateien auf jeweils eine einzelne Datei beschränkt oder wenn die Komponente nur interaktives clientseitiges Rendering (CSR, InteractiveWebAssembly
) übernimmt, kann die Komponente die Verwendung von LazyBrowserFileStream
vermeiden und Stream verwenden. Im Folgenden werden die Änderungen für die FileUpload2
Komponente dargestellt:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Entfernen Sie die LazyBrowserFileStream
Klasse (LazyBrowserFileStream.cs
), da sie nicht verwendet wird.
Wenn die Komponente das Hochladen von Dateien auf jeweils eine einzelne Datei beschränkt, kann die Komponente die Verwendung von LazyBrowserFileStream
vermeiden und Stream verwenden. Im Folgenden werden die Änderungen für die FileUpload2
Komponente dargestellt:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Entfernen Sie die LazyBrowserFileStream
Klasse (LazyBrowserFileStream.cs
), da sie nicht verwendet wird.
Der folgende Controller im Web-API-Projekt speichert vom Client hochgeladene Dateien.
Wichtig
Der Controller in diesem Abschnitt ist für die Verwendung in einem separaten Web-API-Projekt über die Blazor-App vorgesehen. Die Web-API sollte XSRF/CSRF-Angriffe (Cross-Site Request Forgery, websiteübergreifende Anforderungsfälschung) mindern, wenn Benutzer*innen für den Dateiupload authentifiziert werden.
Hinweis
Das Binden von Formularwerten mit dem [FromForm]
-Attribut wird für minimale APIs in ASP.NET Core in .NET 6 nicht nativ unterstützt. Daher kann das folgende Beispiel des Controllers Filesave
nicht für die Verwendung minimaler APIs konvertiert werden. Unterstützung für die Bindung von Formularwerten mit minimalen APIs ist in ASP.NET Core ab .NET 7 verfügbar.
Erstellen Sie zum Verwenden des folgenden Codes einen Ordner Development/unsafe_uploads
im Stammverzeichnis des Web-API-Projekts für die App, die in der Development
-Umgebung ausgeführt wird.
Da im Beispiel die Umgebung der App als Teil des Pfads verwendet wird, unter dem Dateien gespeichert werden, sind zusätzliche Ordner erforderlich, wenn andere Umgebungen in Test- und Produktionsumgebungen verwendet werden. Erstellen Sie beispielsweise einen Staging/unsafe_uploads
-Ordner für die Staging
-Umgebung. Erstellen Sie einen Production/unsafe_uploads
-Ordner für die Production
-Umgebung.
Warnung
Im Beispiel werden Dateien gespeichert, ohne ihre Inhalte zu überprüfen, und in den Anleitungen dieses Artikels werden keine zusätzlichen bewährten Sicherheitsmethoden für hochgeladene Dateien berücksichtigt. Deaktivieren Sie bei Staging- und Produktionssystemen die Ausführungsberechtigung für den Uploadordner, und prüfen Sie Dateien unmittelbar nach dem Upload mit einer Antiviren-/Antischadsoftwarescanner-API. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
Controllers/FilesaveController.cs
:
using System.Net;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[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 = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
Im obigen Code wird GetRandomFileName aufgerufen, um einen sicheren Dateinamen zu generieren. Verlassen Sie sich niemals auf den vom Browser angegebenen Dateinamen, denn Cyberangreifende könnten einen Dateinamen wählen, der eine vorhandene Datei überschreibt, oder einen Pfad senden, der versucht, außerhalb der Anwendung zu schreiben.
Die Server-App muss Controllerdienste registrieren und Controllerendpunkte zuordnen. Weitere Informationen finden Sie unter Routing zu Controlleraktionen in ASP.NET Core.
Dateien auf einen Server mit clientseitigem Rendering (CSR) hochladen
Dieser Abschnitt gilt für clientseitig gerenderte (CSR) Komponenten in Blazor Web Apps oder Blazor WebAssemblyAnwendungen.
Das folgende Beispiel zeigt das Hochladen von Dateien auf einen Backend-Web-API-Controller in einer separaten Anwendung, möglicherweise auf einem separaten Server, von einer Komponente in einer Blazor Web App, die CSR übernimmt, oder einer Komponente in einer Blazor WebAssembly-Anwendung.
Die folgende UploadResult
-Klasse verwaltet das Ergebnis einer hochgeladenen Datei. Wenn eine Datei nicht auf den Server hochgeladen werden kann, wird in ErrorCode
ein Fehlercode zurückgegeben, der dem Benutzer angezeigt werden kann. Auf dem Server wird für jede Datei ein sicherer Dateiname generiert und zur Anzeige an den Client in StoredFileName
zurückgegeben. Dateien werden zwischen dem Client und dem Server mit dem unsicheren/nicht vertrauenswürdigen Dateinamen in FileName
verschlüsselt.
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; }
}
Hinweis
Die vorangehende Klasse UploadResult
kann von client- und serverbasierten Projekten gemeinsam genutzt werden. Wenn Client- und Serverprojekte die Klasse gemeinsam nutzen, fügen Sie einen Import zu jeder _Imports.razor
-Datei des App-Projekts für das freigegebene Projekt hinzu. Beispiel:
@using BlazorSample.Shared
Die folgende FileUpload2
-Komponente:
- Ermöglicht Benutzern das Hochladen von Dateien vom Client.
- zeigt den nicht vertrauenswürdigen bzw. unsicheren Dateinamen an, der vom Client auf der Benutzeroberfläche angegeben wird. Der nicht vertrauenswürdige bzw. unsichere Dateiname wird automatisch von Razor HTML-codiert, um die sichere Anzeige auf der Benutzeroberfläche zu ermöglichen.
Eine bewährte Sicherheitsmethode für Produktions-Apps besteht darin, das Senden von Fehlermeldungen an Clients zu vermeiden, die vertrauliche Informationen über eine App, einen Server oder ein Netzwerk offenlegen könnten. Die Bereitstellung ausführlicher Fehlermeldungen kann einen böswilligen Benutzer bei der Entwicklung von Angriffen auf eine App, einen Server oder ein Netzwerk unterstützen. Der Beispielcode in diesem Abschnitt sendet nur eine Fehlercodenummer (int
) zur Anzeige durch die clientseitige Komponente, wenn ein serverseitiger Fehler auftritt. Wenn ein Benutzer Hilfe bei einem Dateiupload benötigt, gibt dieser den Fehlercode zur Lösung eines Supporttickets an das Supportpersonal weiter, ohne jemals selbst etwas über die genaue Ursache des Fehlers zu erhalten.
Warnung
Vertrauen Sie von Clients bereitgestellte Dateinamen für folgende Zwecke nicht:
- Speichern der Datei in einem Dateisystem oder Dienst
- Anzeigen auf Benutzeroberflächen, die Dateinamen weder automatisch oder über Entwicklercode codieren
Weitere Informationen zu Sicherheitsüberlegungen beim Hochladen von Dateien auf einen Server finden Sie unter Hochladen von Dateien in ASP.NET Core.
Im Blazor Web App-Hauptprojekt fügen Sie IHttpClientFactory und die zugehörigen Dienste in die Program
-Datei des Projekts ein:
builder.Services.AddHttpClient();
Die HttpClient
-Dienste müssen dem Hauptprojekt hinzugefügt werden, da die clientseitige Komponente auf dem Server vorab gerendert wird. Wenn Sie das Vorabrendering für die folgende Komponente deaktivieren, müssen Sie die HttpClient
-Dienste in der Haupt-App nicht bereitstellen und die vorangehende Zeile nicht zum Hauptprojekt hinzufügen.
Weitere Informationen zum Hinzufügen von HttpClient
-Diensten zu einer ASP.NET Core-App finden Sie unter Übermitteln von HTTP-Anforderungen mithilfe von IHttpClientFactory in ASP.NET Core.
Das Client-Projekt (.Client
) einer Blazor Web App muss auch einen HttpClient für HTTP-POST-Anfragen an einen Back-End-Web-API-Controller registrieren. Überprüfen Sie Folgendes in der Datei Program
des Clientprojekts, oder fügen Sie es hinzu:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
Im vorherigen Beispiel wird die Basisadresse mit builder.HostEnvironment.BaseAddress
(IWebAssemblyHostEnvironment.BaseAddress) festgelegt, wodurch die Basisadresse für die App abgerufen und in der Regel vom href
-Wert des <base>
-Tags auf der Hostseite abgeleitet wird. Wenn Sie eine externe Web-API aufrufen, legen Sie den URI auf die Basisadresse der Web-API fest.
Geben Sie das Attribut Interaktiver WebAssembly-Rendermodus oben in der folgenden Komponente in einem Blazor Web App an:
@rendermode InteractiveWebAssembly
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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)
{
try
{
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; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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)
{
try
{
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; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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)
{
try
{
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; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@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)
{
try
{
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; }
}
}
Der folgende Controller im serverseitigen Projekt speichert vom Client hochgeladene Dateien.
Hinweis
Das Binden von Formularwerten mit dem [FromForm]
-Attribut wird für minimale APIs in ASP.NET Core in .NET 6 nicht nativ unterstützt. Daher kann das folgende Beispiel des Controllers Filesave
nicht für die Verwendung minimaler APIs konvertiert werden. Unterstützung für die Bindung von Formularwerten mit minimalen APIs ist in ASP.NET Core ab .NET 7 verfügbar.
Erstellen Sie einen Ordner Development/unsafe_uploads
im Stammverzeichnis des serverseitigen Projekts für die App, die in der Development
-Umgebung ausgeführt wird, um den folgenden Code zu verwenden.
Da im Beispiel die Umgebung der App als Teil des Pfads verwendet wird, unter dem Dateien gespeichert werden, sind zusätzliche Ordner erforderlich, wenn andere Umgebungen in Test- und Produktionsumgebungen verwendet werden. Erstellen Sie beispielsweise einen Staging/unsafe_uploads
-Ordner für die Staging
-Umgebung. Erstellen Sie einen Production/unsafe_uploads
-Ordner für die Production
-Umgebung.
Warnung
Im Beispiel werden Dateien gespeichert, ohne ihre Inhalte zu überprüfen, und in den Anleitungen dieses Artikels werden keine zusätzlichen bewährten Sicherheitsmethoden für hochgeladene Dateien berücksichtigt. Deaktivieren Sie bei Staging- und Produktionssystemen die Ausführungsberechtigung für den Uploadordner, und prüfen Sie Dateien unmittelbar nach dem Upload mit einer Antiviren-/Antischadsoftwarescanner-API. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
Aktualisieren Sie im folgenden Beispiel den Namespace des freigegebenen Projekts so, dass er mit dem freigegebenen Projekt übereinstimmt, wenn die UploadResult
-Klasse von einem freigegebenen Projekt bereitgestellt wird.
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[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 = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
Im obigen Code wird GetRandomFileName aufgerufen, um einen sicheren Dateinamen zu generieren. Verlassen Sie sich niemals auf den vom Browser angegebenen Dateinamen, denn Cyberangreifende könnten einen Dateinamen wählen, der eine vorhandene Datei überschreibt, oder einen Pfad senden, der versucht, außerhalb der Anwendung zu schreiben.
Die Server-App muss Controllerdienste registrieren und Controllerendpunkte zuordnen. Weitere Informationen finden Sie unter Routing zu Controlleraktionen in ASP.NET Core.
Abbrechen eines Dateiuploads
Eine Dateiuploadkomponente kann erkennen, wenn ein Benutzer einen Upload mithilfe eines CancellationToken beim Aufruf von IBrowserFile.OpenReadStream oder StreamReader.ReadAsync abgebrochen hat.
Erstellen Sie eine CancellationTokenSource für die InputFile
-Komponente. Überprüfen Sie am Anfang der OnInputFileChange
-Methode, ob ein vorheriger Upload ausgeführt wird.
Wenn ein Dateiupload ausgeführt wird:
- Rufen Sie Cancel für den vorherigen Upload auf.
- Erstellen Sie eine neue CancellationTokenSource für den nächsten Upload, und übergeben Sie das CancellationTokenSource.Token an OpenReadStream oder ReadAsync.
Serverseitiges Hochladen von Dateien mit Fortschritt
Im folgenden Beispiel wird gezeigt, wie Dateien in eine serverseitige App hochgeladen werden, deren Uploadstatus für Benutzer*innen angezeigt wird.
So verwenden Sie das folgende Beispiel in einer Test-App:
- Erstellen Sie einen Ordner, um hochgeladene Dateien für die
Development
-Umgebung zu speichern:Development/unsafe_uploads
. - Konfigurieren Sie die maximale Dateigröße (
maxFileSize
, 15 KB im folgenden Beispiel) und die maximal zulässige Anzahl Dateien (maxAllowedFiles
, 3 im folgenden Beispiel). - Legen Sie den Puffer auf einen anderen Wert fest (10 KB im folgenden Beispiel), um die Granularität bei der Berichterstellung zu erhöhen. Mit Blick auf die Leistung und Sicherheit wird die Verwendung eines Puffers von mehr als 30 KB nicht empfohlen.
Warnung
Im Beispiel werden Dateien gespeichert, ohne ihre Inhalte zu überprüfen, und in den Anleitungen dieses Artikels werden keine zusätzlichen bewährten Sicherheitsmethoden für hochgeladene Dateien berücksichtigt. Deaktivieren Sie bei Staging- und Produktionssystemen die Ausführungsberechtigung für den Uploadordner, und prüfen Sie Dateien unmittelbar nach dem Upload mit einer Antiviren-/Antischadsoftwarescanner-API. Weitere Informationen finden Sie unter Hochladen von Dateien in ASP.NET Core.
FileUpload3.razor
:
@page "/file-upload-3"
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<PageTitle>File Upload 3</PageTitle>
<h1>File Upload Example 3</h1>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
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);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
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;
}
}
@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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
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;
}
}
@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>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@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))
{
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;
}
}
Weitere Informationen finden Sie in den folgenden API-Ressourcen:
- FileStream: Stellt einen Stream für eine Datei bereit, wobei synchrone und asynchrone Lese- und Schreibvorgänge unterstützt werden.
- FileStream.ReadAsync: Die obige
FileUpload3
-Komponente liest den Datenstrom mit ReadAsync asynchron. Das synchrone Lesen eines Datenstroms mit Read wird in Razor-Komponenten nicht unterstützt.
Dateistreams
Bei Serverinteraktivität werden Dateidaten über die SignalR-Verbindung in .NET-Code auf dem Server gestreamt, während die Datei gelesen wird.
RemoteBrowserFileStreamOptions ermöglicht das Konfigurieren von Dateiuploadeigenschaften.
Bei einer mit WebAssembly gerenderten Komponente werden die Dateidaten direkt in den .NET-Code im Browser gestreamt.
Bildvorschau beim Hochladen
Wenn Sie eine Bildvorschau beim Hochladen von Bildern wünschen, fügen Sie zunächst eine InputFile
-Komponente mit einem Komponentenverweis und einem OnChange
-Handler hinzu:
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Fügen Sie ein Bildelement mit einem Elementverweis hinzu, der als Platzhalter für die Bildvorschau dient:
<img @ref="previewImageElem" />
Fügen Sie die zugehörigen Verweise hinzu:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}
Fügen Sie in JavaScript eine Funktion hinzu, die mit einem HTML-input
- und img
-Element aufgerufen wird und folgende Schritte ausführt:
- Extrahieren der ausgewählten Datei
- Erstellen einer Objekt-URL mit
createObjectURL
- Festlegen eines Ereignislisteners, der die Objekt-URL nach dem Laden des Bilds mit
revokeObjectURL
widerruft, damit es nicht zu Arbeitsspeicherverlusten kommt - Festlegen der Quelle des
img
-Elements, um das Bild anzuzeigen
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
}
Verwenden Sie zum Schluss eine eingefügte IJSRuntime, um den OnChange
-Handler hinzuzufügen, der die JavaScript-Funktion aufruft:
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
Im vorherigen Beispiel wird ein einzelnes Bild hochgeladen. Der Ansatz kann erweitert werden, um multiple
-Bilder zu unterstützen.
Die folgende FileUpload4
-Komponente zeigt das vollständige Beispiel.
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);
}
@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);
}
Hochladen von Dateien in einen externen Dienst
Anstatt dass eine App die Bytes für das Hochladen von Dateien verarbeitet und der Server der App die hochgeladenen Dateien empfängt, können Clients Dateien direkt in einen externen Dienst hochladen. Die App kann die Dateien des externen Diensts bedarfsgesteuert und sicher verarbeiten. Dieser Ansatz schützt die App und ihren Server vor böswilligen Angriffen und möglichen Leistungsproblemen.
Erwägen Sie einen Ansatz, bei dem Azure Files, Azure Blob Storage oder ein Dienst eines Drittanbieters mit den folgenden potenziellen Vorteilen zum Einsatz kommt:
- Hochladen von Dateien direkt vom Client in einen externen Dienst mithilfe einer JavaScript-Clientbibliothek oder REST API. Azure bietet beispielsweise die folgenden Clientbibliotheken und APIs:
- Autorisieren von Benutzeruploads mit einem vom Benutzer delegierten SAS-Token (Shared Access Signature), das von der App (serverseitig) für jeden Upload der Clientdatei generiert wird. Azure bietet beispielsweise die folgenden SAS-Features:
- Bereitstellen von automatischer Redundanz und Dateifreigabesicherungen.
- Begrenzen von Uploads mithilfe von Kontingenten. Beachten Sie, dass Azure Blob Storage-Kontingente auf Kontoebene und nicht auf Containerebene festgelegt werden. Azure Files-Kontingente gelten jedoch auf Dateifreigabeebene und bieten möglicherweise eine bessere Steuerung von Grenzwerten für Uploads. Weitere Informationen finden Sie unter den Links zu den Azure-Dokumenten weiter oben.
- Absichern von Dateien mit serverseitiger Verschlüsselung (SSE).
Weitere Informationen zu Azure Blob Storage und Azure Files finden Sie in der Dokumentation zu Azure Storage.
Serverseitiger Grenzwert für die SignalR-Nachrichtengröße
Das Hochladen von Dateien kann fehlschlagen, ehe es überhaupt begonnen hat, wenn Blazor Daten über die Dateien abruft, die die maximale Nachrichtengröße SignalR überschreiten.
SignalR definiert ein Grenzwert für die Nachrichtengröße, der für jede Nachricht gilt, die Blazor empfängt, und die InputFile-Komponente streamt Dateien in Nachrichten an den Server, die den konfigurierten Grenzwert einhalten. Die erste Nachricht, die angibt, welche Dateien hochgeladen werden sollen, wird jedoch als eindeutige einzelne Nachricht gesendet. Die Größe der ersten Nachricht kann den Grenzwert für die Nachrichtengröße von SignalR überschreiten. Das Problem hat nichts mit der Größe der Dateien zu tun, sondern mit ihrer Anzahl.
Die protokollierte Fehlermeldung lautet etwa folgendermaßen:
Fehler: Connection disconnected with error „Error: Server returned an error on close: Connection closed with an error.“ (Die Verbindung wurde durch den folgenden Fehler getrennt: „Der Server hat beim Schließen einen Fehler zurückgegeben: Die Verbindung wurde durch einen Fehler beendet.“) e.log @ blazor.server.js:1
Beim Hochladen von Dateien wird der Grenzwert für die Nachrichtengröße nur selten schon bei der ersten Nachricht erreicht. Wenn der Grenzwert erreicht ist, kann die App HubOptions.MaximumReceiveMessageSize mit einem größeren Wert konfigurieren.
Weitere Informationen zur SignalR-Konfiguration und zum Festlegen von MaximumReceiveMessageSizefinden Sie unter ASP.NET Core BlazorSignalR-Anleitungen.
Maximal auszuführende parallele Aufrufe pro Clienthubeinstellung
Blazor setzt voraus, dass MaximumParallelInvocationsPerClient auf 1 (Standardwert) festgelegt ist.
Das Erhöhen des Werts führt zu einer hohen Wahrscheinlichkeit, dass CopyTo
-Vorgänge System.InvalidOperationException: 'Reading is not allowed after reader was completed.'
auslösen. Weitere Informationen finden Sie unter MaximumParallelInvocationsPerClient > 1 unterbricht den Dateiupload im Blazor Server-Modus (dotnet/aspnetcore
#53951).
Problembehandlung
Die Zeile, die IBrowserFile.OpenReadStream aufruft, löst einen System.TimeoutException aus:
System.TimeoutException: Did not receive any data in the allotted time.
Mögliche Ursachen:
Es wird der Autofac Inversion of Control (IoC)-Container anstelle des integrierten ASP.NET Core-Containers für die Einfügung von Abhängigkeiten verwendet. Um das Problem zu beheben, legen Sie DisableImplicitFromServicesParameters auf
true
in den serverseitigen Schaltkreis-Handler-Hub-Optionen fest. Weitere Informationen finden Sie unter FileUpload: Es wurden innerhalb der vorgegebenen Zeit keine Daten empfangen (dotnet/aspnetcore
#38842).Der Stream wird nicht vollständig gelesen. Das ist kein Framework-Problem. Die Ausnahme einfangen und in Ihrer lokalen Umgebung/Ihrem lokalen Netzwerk weiter untersuchen.
- Verwendung von serverseitigem Rendering und OpenReadStream Aufruf mehrerer Dateien, bevor sie vollständig gelesen werden. Um das Problem zu beheben, verwenden Sie die
LazyBrowserFileStream
Klasse und den Ansatz, die im Abschnitt Dateien auf einen Server mit serverseitigem Rendering hochladen dieses Artikels beschrieben werden.