ASP.NET Core Blazor-Dateiuploads

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-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.

Informationen zum aktuellen Release finden Sie in der .NET 8-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 wird als <input>-HTML-Element vom Typ file gerendert. Standardmäßig wählt der Benutzer einzelne Dateien aus. 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 Vorgänge können zu Leistungs- und Sicherheitsproblemen 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.

Nicht unterstützt: Der folgende Ansatz wird NICHT empfohlen, da der Stream-Inhalt der Datei im Arbeitsspeicher in String gelesen wird (reader):

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

Nicht unterstützt: Der folgende Ansatz wird für Microsoft Azure Blob StorageNICHT empfohlen, da der Stream-Inhalt der Datei im Arbeitsspeicher in MemoryStream kopiert wird (memoryStream), bevor UploadBlobAsync aufgerufen wird:

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

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());

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.

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:

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:

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 interaktive Serverkomponenten in Blazor-Web-Apps.

Dieser Abschnitt gilt für Blazor Server-Apps.

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; }
}

Hinweis

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 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.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 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.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<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 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.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<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 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.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<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 client = ClientFactory.CreateClient();

            var response = 
                await client.PostAsync("https://localhost:5001/Filesave", 
                content);

            if (response.IsSuccessStatusCode)
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                };

                using var responseStream =
                    await response.Content.ReadAsStreamAsync();

                var newUploadResults = await JsonSerializer
                    .DeserializeAsync<IList<UploadResult>>(responseStream, options);

                uploadResults = uploadResults.Concat(newUploadResults).ToList();
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName);

        if (result is null)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result = new();
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string Name { get; set; }
    }
}

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 : ControllerBase
{
    private readonly IHostEnvironment env;
    private readonly ILogger<FilesaveController> logger;

    public FilesaveController(IHostEnvironment env, 
        ILogger<FilesaveController> logger)
    {
        this.env = env;
        this.logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult<IList<UploadResult>>> PostFile(
        [FromForm] IEnumerable<IFormFile> files)
    {
        var maxAllowedFiles = 3;
        long maxFileSize = 1024 * 15;
        var filesProcessed = 0;
        var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
        List<UploadResult> uploadResults = new();

        foreach (var file in files)
        {
            var uploadResult = new UploadResult();
            string trustedFileNameForFileStorage;
            var untrustedFileName = file.FileName;
            uploadResult.FileName = untrustedFileName;
            var trustedFileNameForDisplay =
                WebUtility.HtmlEncode(untrustedFileName);

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                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. Vertrauen Sie niemals dem vom Browser bereitgestellten Dateinamen, da ein Angreifer möglicherweise einen vorhandenen Dateinamen auswählt, mit dem eine vorhandene Datei überschrieben wird, oder einen Pfad senden kann, mit dem versucht wird, außerhalb der App zu schreiben.

Die Server-App muss Controllerdienste registrieren und Controllerendpunkte zuordnen. Weitere Informationen finden Sie unter Routing zu Controlleraktionen in ASP.NET Core.

Hochladen von Dateien auf einen Server

Im folgenden Beispiel wird das Hochladen von Dateien in einen Web-API-Controller.

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.

Fügen Sie im Hauptprojekt der Blazor-Web-App IHttpClientFactory und die zugehörigen Dienste in der Datei Program des Projekts hinzu:

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 Clientprojekt (.Client) einer Blazor-Web-App muss auch HttpClient für HTTP POST-Anforderungen 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 für den interaktiven WebAssembly-Rendermodus oben in der folgenden Komponente einer 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 : ControllerBase
{
    private readonly IHostEnvironment env;
    private readonly ILogger<FilesaveController> logger;

    public FilesaveController(IHostEnvironment env,
        ILogger<FilesaveController> logger)
    {
        this.env = env;
        this.logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult<IList<UploadResult>>> PostFile(
        [FromForm] IEnumerable<IFormFile> files)
    {
        var maxAllowedFiles = 3;
        long maxFileSize = 1024 * 15;
        var filesProcessed = 0;
        var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
        List<UploadResult> uploadResults = new();

        foreach (var file in files)
        {
            var uploadResult = new UploadResult();
            string trustedFileNameForFileStorage;
            var untrustedFileName = file.FileName;
            uploadResult.FileName = untrustedFileName;
            var trustedFileNameForDisplay =
                WebUtility.HtmlEncode(untrustedFileName);

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                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. Vertrauen Sie niemals dem vom Browser bereitgestellten Dateinamen, da ein Angreifer möglicherweise einen vorhandenen Dateinamen auswählt, mit dem eine vorhandene Datei überschrieben wird, oder einen Pfad senden kann, mit dem versucht wird, außerhalb der App 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:

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:

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.

Zusätzliche Ressourcen