Sdílet prostřednictvím


nahrání souborů ASP.NET Core Blazor

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 10 tohoto článku.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

Tento článek vysvětluje, jak nahrát soubory do BlazorInputFile komponenty.

Nahrání souborů

Upozorňující

Při povolování nahrávání souborů vždy dodržujte osvědčené postupy zabezpečení. Další informace najdete v tématu Nahrání souborů v ASP.NET Core.

InputFile Pomocí komponenty můžete číst data souboru prohlížeče do kódu .NET. Komponenta InputFile vykresluje element HTML <input> typu file pro nahrání jednoho souboru. multiple Přidejte atribut, který uživateli umožní nahrávat více souborů najednou.

Výběr souboru není kumulativní při použití InputFile komponenty nebo jeho základního html <input type="file">, takže nemůžete přidat soubory do existujícího výběru souboru. Komponenta vždy nahradí počáteční výběr souboru uživatele, takže odkazy na soubory z předchozích výběrů nejsou k dispozici.

InputFile Následující komponenta spustí metoduLoadFiles, když OnChange dojde k události (change). Poskytuje InputFileChangeEventArgs přístup k vybranému seznamu souborů a podrobnostem o jednotlivých souborech:

<InputFile OnChange="LoadFiles" multiple />

@code {
    private void LoadFiles(InputFileChangeEventArgs e)
    {
        ...
    }
}

Vykreslené HTML:

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

Poznámka:

V předchozím příkladu se atribut elementu <input>_bl_2 používá pro Blazorinterní zpracování.

Pokud chcete číst data ze souboru vybraného uživatelem pomocí Stream, který představuje bajty souboru, zavolejte IBrowserFile.OpenReadStream v souboru a načtěte z vráceného datového proudu. Další informace najdete v části Streamy souborů.

OpenReadStream vynucuje maximální velikost v bajtech jeho Stream. Při čtení jednoho nebo více souborů větších než 500 kB dojde k výjimce. Tento limit brání vývojářům v náhodném čtení velkých souborů do paměti. Parametr maxAllowedSizeOpenReadStream lze v případě potřeby použít k určení větší velikosti.

Mimo zpracování malého souboru nepoužívejte čtení příchozího datového proudu souboru přímo do paměti najednou. Například nekopírujte všechny bajty souboru do MemoryStream celého datového proudu ani nečtěte celý datový proud do pole bajtů najednou. Tyto přístupy můžou vést ke snížení výkonu aplikace a potenciálnímu riziku útoku DoS (DoS), zejména u komponent na straně serveru. Místo toho zvažte přijetí některého z následujících přístupů:

  • Zkopírujte stream přímo do souboru na disku, aniž byste ho museli číst do paměti. Všimněte si, že Blazor aplikace, které spouští kód na serveru, nemají přímý přístup k systému souborů klienta.
  • Nahrajte soubory z klienta přímo do externí služby. Další informace najdete v části Nahrání souborů do externí služby .

V následujících příkladech browserFile implementuje IBrowserFile představující nahraný soubor. Pracovní implementace pro IBrowserFile soubory se zobrazují v komponentách pro nahrání souborů dále v tomto článku.

Při volání OpenReadStreamdoporučujeme předat maximální povolenou velikost souboru v parametru maxAllowedSize na limitu velikostí souborů, které očekáváte. Výchozí hodnota je 500 kB. V příkladech tohoto článku se používá maximální povolená proměnná velikosti souboru nebo konstanta s názvem maxFileSize ale obvykle se nezobrazuje nastavení konkrétní hodnoty.

Podporováno: Následující přístup se doporučuje , protože soubor Stream je poskytován přímo příjemci, FileStream který vytvoří soubor na zadané cestě:

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

await blobContainerClient.UploadBlobAsync(
    trustedFileName, browserFile.OpenReadStream(maxFileSize));

✔️ Doporučuje se jenom pro malé soubory: Následující přístup se doporučuje jenom pro malé soubory, protože se obsah Stream souboru čte do MemoryStream paměti (memoryStream), což způsobuje snížení výkonnosti a riziko DoS. Příklad, který ukazuje tuto techniku uložení miniatury s IBrowserFile do databáze pomocí Entity Framework Core (EF Core), viz část Uložit malé soubory přímo do databáze s EF Core dále v tomto článku.

using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
var smallFileByteArray = memoryStream.ToArray();

Nedoporučuje se: Následující přístup se nedoporučuje , protože obsah souboru Stream je načten do String paměti (reader):

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

Nedoporučuje se: Pro Microsoft Azure Blob Storage se nedoporučuje následující přístup, protože obsah souboru Stream se před voláním MemoryStreammemoryStreamzkopíruje do paměti (UploadBlobAsync):

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

Komponenta, která přijímá soubor obrázku, může volat metodu BrowserFileExtensions.RequestImageFileAsync usnadnění v souboru, aby změnila velikost dat obrázků v modulu runtime JavaScript prohlížeče před tím, než se obrázek streamuje do aplikace. Případy použití pro volání RequestImageFileAsync jsou nejvhodnější pro Blazor WebAssembly aplikace.

Omezení čtení a nahrávání velikosti souboru

U prohlížečů založených na Chromiu (například Google Chrome a Microsoft Edge) používajících protokol HTTP/2, HTTPS a CORS podporuje klientská strana Blazor pomocí rozhraní API Streams , aby bylo možné nahrávat velké soubory se streamováním požadavků.

Bez prohlížeče Chromium, protokolu HTTP/2 nebo HTTPS klientská strana Blazor čte bajty souboru do jedné vyrovnávací paměti pole JavaScriptu při převodu dat z JavaScriptu do jazyka C#. Tato vyrovnávací paměť je omezená na 2 GB nebo na dostupnou paměť zařízení. Při nahrávání velkých souborů na straně klienta pomocí komponenty InputFile může dojít k selhání.

Na straně klienta Blazor se při zařazování dat z JavaScriptu do C# čtou bajty souboru do jedné vyrovnávací paměti pole JavaScriptu, která je omezená na 2 GB nebo na dostupnou paměť zařízení. Při nahrávání velkých souborů na straně klienta pomocí komponenty InputFile může dojít k selhání. Doporučujeme přijmout streamování požadavků pomocí .NET 9 nebo novějšího.

Bezpečnostní aspekty

Vyhněte se IBrowserFile.Size omezením velikosti souborů

IBrowserFile.Size Nepoužívejte omezení velikosti souboru. Místo použití nebezpečné velikosti souboru zadaného klientem explicitně zadejte maximální velikost souboru. Následující příklad používá maximální velikost souboru přiřazenou:maxFileSize

- var fileContent = new StreamContent(file.OpenReadStream(file.Size));
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));

Zabezpečení názvu souboru

Nikdy nepoužívejte název souboru zadaného klientem k uložení souboru do fyzického úložiště. Vytvořte bezpečný název souboru pomocí Path.GetRandomFileName() nebo Path.GetTempFileName() vytvořte úplnou cestu (včetně názvu souboru) pro dočasné úložiště.

Razor automaticky HTML kóduje hodnoty vlastností pro zobrazení. Následující kód je bezpečný pro použití:

@foreach (var file in Model.DatabaseFiles) {
    <tr>
        <td>
            @file.UntrustedName
        </td>
    </tr>
}

Razor Mimo Razor vždy používejte k bezpečnému kódování názvů souborů z požadavku uživatele.

Mnoho implementací musí obsahovat kontrolu, že soubor existuje; v opačném případě se soubor přepíše souborem se stejným názvem. Zadejte další logiku, která vyhovuje specifikacím vaší aplikace.

Příklady

Následující příklady ukazují, že v komponentě je nahrání více souborů. InputFileChangeEventArgs.GetMultipleFiles umožňuje čtení více souborů. Zadejte maximální počet souborů, které zabrání uživateli se zlými úmysly nahrát větší počet souborů, než aplikace očekává. InputFileChangeEventArgs.File umožňuje čtení prvního a jediného souboru, pokud nahrávání souboru nepodporuje více souborů.

InputFileChangeEventArgs je v Microsoft.AspNetCore.Components.Forms oboru názvů, což je obvykle jeden z oborů názvů v souboru aplikace _Imports.razor . Když je v _Imports.razor souboru přítomen obor názvů, poskytuje členovi rozhraní API přístup ke komponentám aplikace.

Obory názvů v _Imports.razor souboru se neuplatní u souborů jazyka C# (.cs). Soubory jazyka C# vyžadují explicitní using direktivu v horní části souboru třídy:

using Microsoft.AspNetCore.Components.Forms;

Pro testování součástí nahrávání souborů můžete pomocí PowerShellu vytvořit testovací soubory libovolné velikosti:

$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)

V předcházejícím příkazu:

  • Zástupný {SIZE} symbol je velikost souboru v bajtech (například 2097152 pro soubor o velikosti 2 MB).
  • Zástupný {PATH} symbol je cesta a soubor s příponou souboru (například D:/test_files/testfile2MB.txt).

Příklad nahrání souboru na straně serveru

Pokud chcete použít následující kód, vytvořte Development/unsafe_uploads složku v kořenovém adresáři aplikace spuštěné v Development prostředí.

Vzhledem k tomu, že v příkladu se prostředí aplikace používá jako součást cesty, do které se ukládají soubory, jsou vyžadovány další složky, pokud se v testovacím a produkčním prostředí používají jiná prostředí. Vytvořte například Staging/unsafe_uploads složku pro Staging prostředí. Vytvořte Production/unsafe_uploads složku pro Production prostředí.

Upozorňující

Příklad ukládá soubory bez skenování jejich obsahu a pokyny v tomto článku nebere v úvahu další osvědčené postupy zabezpečení pro nahrané soubory. V přípravných a produkčních systémech zakažte oprávnění ke spuštění složky pro nahrávání a skenování souborů pomocí rozhraní API antivirového nebo antimalwarového skeneru okamžitě po nahrání. Další informace najdete v tématu Nahrání souborů v 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 = [];
    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;
    }
}

Příklad nahrání souboru na straně klienta

Následující příklad zpracovává bajty souborů a neodesílá soubory do cíle mimo aplikaci. Příklad Razor komponenty, která odesílá soubor na server nebo službu, najdete v následujících částech:

Komponenta předpokládá, že režim vykreslování Interactive WebAssembly (InteractiveWebAssembly) je zděděný z nadřazené komponenty nebo použit globálně v aplikaci.

@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 = [];
    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 vrátí metadata vystavená prohlížečem jako vlastnosti. Tato metadata použijte k předběžnému ověření.

Nikdy nedůvěřujte hodnotám předchozích vlastností, zejména Name vlastnosti pro zobrazení v uživatelském rozhraní. Zacházejte s daty zadanými uživatelem jako s významnými bezpečnostními riziky pro aplikaci, server a síť. Další informace najdete v tématu Nahrání souborů v ASP.NET Core.

Nahrání souborů na server s vykreslováním na straně serveru

Tato část se týká součástí Interaktivního serveru v Blazor Web Appaplikacích nebo Blazor Server aplikacích.

Následující příklad ukazuje nahrání souborů z aplikace na straně serveru do kontroleru back-endového webového rozhraní API v samostatné aplikaci, pravděpodobně na samostatném serveru.

V souboru aplikace Program na straně serveru přidejte IHttpClientFactory a související služby, které aplikaci umožňují vytvářet HttpClient instance:

builder.Services.AddHttpClient();

Další informace najdete v tématu Vytváření požadavků HTTP pomocí IHttpClientFactory v ASP.NET Core.

Příklady v této části:

  • Webové rozhraní API běží na adrese URL: https://localhost:5001
  • Aplikace na straně serveru běží na adrese URL: https://localhost:5003

Pro účely testování jsou předchozí adresy URL nakonfigurované v souborech projektů Properties/launchSettings.json .

Následující UploadResult třída udržuje výsledek nahraného souboru. Pokud se na server nepodaří nahrát soubor, vrátí se uživateli kód chyby, který ErrorCode se zobrazí uživateli. Na serveru pro každý soubor se vygeneruje bezpečný název souboru a vrátí se klientovi StoredFileName pro zobrazení. Soubory jsou klíčovány mezi klientem a serverem pomocí nebezpečného nebo nedůvěryhodného názvu souboru v souboru .FileName

UploadResult.cs:

public class UploadResult
{
    public bool Uploaded { get; set; }
    public string? FileName { get; set; }
    public string? StoredFileName { get; set; }
    public int ErrorCode { get; set; }
}

Osvědčeným postupem zabezpečení pro produkční aplikace je vyhnout se odesílání chybových zpráv klientům, kteří můžou odhalit citlivé informace o aplikaci, serveru nebo síti. Poskytnutí podrobných chybových zpráv může pomoct uživateli se zlými úmysly při řešení útoků na aplikaci, server nebo síť. Ukázkový kód v této části odesílá zpět pouze číslo kódu chyby (int) pro zobrazení komponentou na straně klienta, pokud dojde k chybě na straně serveru. Pokud uživatel vyžaduje pomoc s nahráním souboru, poskytne pracovníkům podpory kód chyby pro řešení lístků podpory, aniž by věděl, jakou přesnou příčinu chyby má.

Následující LazyBrowserFileStream třída definuje vlastní typ streamu, který lazily volání OpenReadStream těsně před prvními bajty datového proudu jsou požadovány. Stream se nepřenáší z prohlížeče na server, dokud čtení datového proudu nezačíná v .NET.

LazyBrowserFileStream.cs:

using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize) 
    : Stream
{
    private readonly IBrowserFile file = file;
    private readonly int maxAllowedSize = maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public override void Flush() => underlyingStream?.Flush();

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen() => 
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}

FileUpload2 Následující komponenta:

  • Umožňuje uživatelům nahrávat soubory z klienta.
  • Zobrazí nedůvěryhodný nebo nebezpečný název souboru poskytnutý klientem v uživatelském rozhraní. Nedůvěryhodný nebo nebezpečný název souboru je automaticky kódován Razor html pro bezpečné zobrazení v uživatelském rozhraní.

Upozorňující

Nedůvěřujte názvům souborů zadaným klienty pro:

  • Uložení souboru do systému souborů nebo služby
  • Zobrazí se v uživatelských rozhraních, které nezakódují názvy souborů automaticky nebo prostřednictvím vývojářského kódu.

Další informace o aspektech zabezpečení při nahrávání souborů na server najdete v tématu Nahrání souborů v ASP.NET Core.

FileUpload2.razor:

@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<PageTitle>File Upload 2</PageTitle>

<h1>File Upload Example 2</h1>

<p>
    This example requires a backend server API to function. For more information, 
    see the <em>Upload files to a server</em> section 
    of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Any())
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = [];
    private List<UploadResult> uploadResults = [];
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        int maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

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

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

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

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

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

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

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

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>File Upload Example 2</h1>

<p>
    This example requires a backend server API to function. For more information, 
    see the <em>Upload files to a server</em> section 
    of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        int maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

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

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

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

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

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

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

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

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>File Upload Example 2</h1>

<p>
    This example requires a backend server API to function. For more information, 
    see the <em>Upload files to a server</em> section 
    of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        int maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

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

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

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

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

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

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

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

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>File Upload Example 2</h1>

<p>
    This example requires a backend server API to function. For more information, 
    see the <em>Upload files to a server</em> section 
    of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        int maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

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

Pokud komponenta omezuje nahrávání souborů na jeden soubor najednou nebo pokud komponenta používá pouze renderování na straně klienta (CSR), může se vyhnout použití InteractiveWebAssembly a použít LazyBrowserFileStream. Následující příklad ukazuje změny komponenty FileUpload2 :

- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));

LazyBrowserFileStream Odeberte třídu (LazyBrowserFileStream.cs), protože se nepoužívá.

Pokud komponenta omezuje nahrávání souboru do jednoho souboru najednou, komponenta se může vyhnout použití LazyBrowserFileStream a použití Stream. Následující příklad ukazuje změny komponenty FileUpload2 :

- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));

LazyBrowserFileStream Odeberte třídu (LazyBrowserFileStream.cs), protože se nepoužívá.

Následující kontroler v projektu webového rozhraní API ukládá nahrané soubory z klienta.

Důležité

Kontroler v této části je určený pro použití v samostatném projektu webového Blazor rozhraní API od aplikace. Webové rozhraní API by mělo zmírnit útoky typu XSRF/CSRF (Cross-Site Request Forgery), pokud se ověřují uživatelé nahrávání souborů.

Poznámka:

Hodnoty formuláře vazby s atributem [FromForm] nejsou nativně podporovány pro minimální rozhraní API v ASP.NET Core v .NET 6. Proto následující Filesave příklad kontroleru nelze převést na použití minimálních rozhraní API. Podpora vazby z hodnot formulářů s minimálními rozhraními API je dostupná v ASP.NET Core v .NET 7 nebo novějším.

Pokud chcete použít následující kód, vytvořte Development/unsafe_uploads složku v kořenovém adresáři projektu webového rozhraní API pro aplikaci spuštěnou v Development prostředí.

Vzhledem k tomu, že v příkladu se prostředí aplikace používá jako součást cesty, do které se ukládají soubory, jsou vyžadovány další složky, pokud se v testovacím a produkčním prostředí používají jiná prostředí. Vytvořte například Staging/unsafe_uploads složku pro Staging prostředí. Vytvořte Production/unsafe_uploads složku pro Production prostředí.

Upozorňující

Příklad ukládá soubory bez skenování jejich obsahu a pokyny v tomto článku nebere v úvahu další osvědčené postupy zabezpečení pro nahrané soubory. V přípravných a produkčních systémech zakažte oprávnění ke spuštění složky pro nahrávání a skenování souborů pomocí rozhraní API antivirového nebo antimalwarového skeneru okamžitě po nahrání. Další informace najdete v tématu Nahrání souborů v ASP.NET Core.

Controllers/FilesaveController.cs:

using System.Net;
using Microsoft.AspNetCore.Mvc;

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

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

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                else
                {
                    try
                    {
                        trustedFileNameForFileStorage = Path.GetRandomFileName();
                        var path = Path.Combine(env.ContentRootPath,
                            env.EnvironmentName, "unsafe_uploads",
                            trustedFileNameForFileStorage);

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

                        logger.LogInformation("{FileName} saved at {Path}",
                            trustedFileNameForDisplay, path);
                        uploadResult.Uploaded = true;
                        uploadResult.StoredFileName = trustedFileNameForFileStorage;
                    }
                    catch (IOException ex)
                    {
                        logger.LogError("{FileName} error on upload (Err: 3): {Message}",
                            trustedFileNameForDisplay, ex.Message);
                        uploadResult.ErrorCode = 3;
                    }
                }

                filesProcessed++;
            }
            else
            {
                logger.LogInformation("{FileName} not uploaded because the " +
                    "request exceeded the allowed {Count} of files (Err: 4)",
                    trustedFileNameForDisplay, maxAllowedFiles);
                uploadResult.ErrorCode = 4;
            }

            uploadResults.Add(uploadResult);
        }

        return new CreatedResult(resourcePath, uploadResults);
    }
}

V předchozím kódu se volá k GetRandomFileName vygenerování zabezpečeného názvu souboru. Nikdy nedůvěřujte názvu souboru poskytnutému prohlížečem, protože cyberattacker může zvolit existující název souboru, který přepíše existující soubor nebo odešle cestu, která se pokusí zapsat mimo aplikaci.

Serverová aplikace musí registrovat služby kontroleru a koncové body kontroleru mapování. Další informace najdete v tématu Směrování na akce kontroleru v ASP.NET Core.

Nahrání souborů na server s vykreslováním na straně klienta (CSR)

Tato část se týká komponent vykreslených na straně klienta (CSR) v Blazor Web Appaplikacích nebo Blazor WebAssembly aplikacích.

Následující příklad ukazuje nahrání souborů do kontroleru back-endového webového rozhraní API v samostatné aplikaci, případně na samostatném serveru, od komponenty v Blazor Web App aplikaci, která přijímá CSR nebo komponentu Blazor WebAssembly v aplikaci.

Příklad přijímá streamování požadavků pro prohlížeč založený na Chromiu (například Google Chrome nebo Microsoft Edge) s protokolem HTTP/2 a HTTPS. Pokud se streamování požadavků nedá použít, Blazor hladce přechází na Fetch API bez streamování požadavků. Další informace najdete v části Omezení pro čtení a nahrávání velikosti souboru .

Následující UploadResult třída udržuje výsledek nahraného souboru. Pokud se na server nepodaří nahrát soubor, vrátí se uživateli kód chyby, který ErrorCode se zobrazí uživateli. Na serveru pro každý soubor se vygeneruje bezpečný název souboru a vrátí se klientovi StoredFileName pro zobrazení. Soubory jsou klíčovány mezi klientem a serverem pomocí nebezpečného nebo nedůvěryhodného názvu souboru v souboru .FileName

UploadResult.cs:

public class UploadResult
{
    public bool Uploaded { get; set; }
    public string? FileName { get; set; }
    public string? StoredFileName { get; set; }
    public int ErrorCode { get; set; }
}

Poznámka:

Předchozí UploadResult třídu je možné sdílet mezi klientem a serverovými projekty. Když projekty klienta a serveru sdílejí třídu, přidejte import do souboru každého projektu _Imports.razor pro sdílený projekt. Příklad:

@using BlazorSample.Shared

FileUpload2 Následující komponenta:

  • Umožňuje uživatelům nahrávat soubory z klienta.
  • Zobrazí nedůvěryhodný nebo nebezpečný název souboru poskytnutý klientem v uživatelském rozhraní. Nedůvěryhodný nebo nebezpečný název souboru je automaticky kódován Razor html pro bezpečné zobrazení v uživatelském rozhraní.

Osvědčeným postupem zabezpečení pro produkční aplikace je vyhnout se odesílání chybových zpráv klientům, kteří můžou odhalit citlivé informace o aplikaci, serveru nebo síti. Poskytnutí podrobných chybových zpráv může pomoct uživateli se zlými úmysly při řešení útoků na aplikaci, server nebo síť. Ukázkový kód v této části odesílá zpět pouze číslo kódu chyby (int) pro zobrazení komponentou na straně klienta, pokud dojde k chybě na straně serveru. Pokud uživatel vyžaduje pomoc s nahráním souboru, poskytne pracovníkům podpory kód chyby pro řešení lístků podpory, aniž by věděl, jakou přesnou příčinu chyby má.

Upozorňující

Nedůvěřujte názvům souborů zadaným klienty pro:

  • Uložení souboru do systému souborů nebo služby
  • Zobrazí se v uživatelských rozhraních, které nezakódují názvy souborů automaticky nebo prostřednictvím vývojářského kódu.

Další informace o aspektech zabezpečení při nahrávání souborů na server najdete v tématu Nahrání souborů v ASP.NET Core.

V projektu serveru Blazor Web App přidejte IHttpClientFactory a související služby do souboru projektu Program.

builder.Services.AddHttpClient();

Služby HttpClient musí být přidány do projektu serveru, protože komponenta na straně klienta je předem na serveru. Pokud zakážete předrenderování pro následující komponentu, nebudete muset poskytovat HttpClient služby v projektu serveru a nemusíte do projektu serveru přidávat předchozí řádek.

Další informace o přidávání HttpClient služeb do aplikace ASP.NET Core najdete v tématu Vytváření požadavků HTTP pomocí IHttpClientFactory v ASP.NET Core.

Klientský projekt (.Client) Blazor Web App klienta musí také zaregistrovat HttpClient požadavky HTTP POST na kontroleru back-endového webového rozhraní API. Potvrďte nebo přidejte následující položky do souboru klientského Program projektu:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

Předchozí příklad nastaví základní adresu na builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), která získá základní adresu aplikace a obvykle se odvozuje od <base> hodnoty značky href na stránce hostitele. Pokud voláte externí webové rozhraní API, nastavte identifikátor URI na základní adresu webového rozhraní API.

Samostatná Blazor WebAssembly aplikace, která nahrává soubory do serverového webového rozhraní API, buď používá pojmenovanou službu HttpClient, nebo nastaví výchozí HttpClient registraci služby tak, aby odkazovala na koncový bod webového rozhraní API. V následujícím příkladu, kde je webové rozhraní API hostované místně na portu 5001, je https://localhost:5001základní adresa:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri("https://localhost:5001") });

V Blazor Web App přidejte obor názvů Microsoft.AspNetCore.Components.WebAssembly.Http do direktiv komponenty:

@using Microsoft.AspNetCore.Components.WebAssembly.Http

FileUpload2.razor:

@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Net
@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)
        {
            using var request = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
            request.SetBrowserRequestStreamingEnabled(true);
            request.Content = content;

            using var response = await Http.SendAsync(request);

            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
@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 = [];
    private List<UploadResult> uploadResults = [];
    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)
        {
            using 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)
        {
            using 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)
        {
            using 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)
        {
            using 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; }
    }
}

Následující kontroler v projektu na straně serveru ukládá nahrané soubory z klienta.

Poznámka:

Hodnoty formuláře vazby s atributem [FromForm] nejsou nativně podporovány pro minimální rozhraní API v ASP.NET Core v .NET 6. Proto následující Filesave příklad kontroleru nelze převést na použití minimálních rozhraní API. Podpora vazby z hodnot formulářů s minimálními rozhraními API je dostupná v ASP.NET Core v .NET 7 nebo novějším.

Pokud chcete použít následující kód, vytvořte Development/unsafe_uploads složku v kořenovém adresáři projektu na straně serveru pro aplikaci spuštěnou v Development prostředí.

Vzhledem k tomu, že v příkladu se prostředí aplikace používá jako součást cesty, do které se ukládají soubory, jsou vyžadovány další složky, pokud se v testovacím a produkčním prostředí používají jiná prostředí. Vytvořte například Staging/unsafe_uploads složku pro Staging prostředí. Vytvořte Production/unsafe_uploads složku pro Production prostředí.

Upozorňující

Příklad ukládá soubory bez skenování jejich obsahu a pokyny v tomto článku nebere v úvahu další osvědčené postupy zabezpečení pro nahrané soubory. V přípravných a produkčních systémech zakažte oprávnění ke spuštění složky pro nahrávání a skenování souborů pomocí rozhraní API antivirového nebo antimalwarového skeneru okamžitě po nahrání. Další informace najdete v tématu Nahrání souborů v ASP.NET Core.

V následujícím příkladu pro hostovanou Blazor WebAssembly aplikaci nebo kde se sdílený projekt používá k zadání UploadResult třídy: přidejte obor názvů sdíleného projektu.

using BlazorSample.Shared;

Doporučujeme použít obor názvů pro následující kontroler (například: namespace BlazorSample.Controllers).

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;

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

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

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                else
                {
                    try
                    {
                        trustedFileNameForFileStorage = Path.GetRandomFileName();
                        var path = Path.Combine(env.ContentRootPath,
                            env.EnvironmentName, "unsafe_uploads",
                            trustedFileNameForFileStorage);

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

                        logger.LogInformation("{FileName} saved at {Path}",
                            trustedFileNameForDisplay, path);
                        uploadResult.Uploaded = true;
                        uploadResult.StoredFileName = trustedFileNameForFileStorage;
                    }
                    catch (IOException ex)
                    {
                        logger.LogError("{FileName} error on upload (Err: 3): {Message}",
                            trustedFileNameForDisplay, ex.Message);
                        uploadResult.ErrorCode = 3;
                    }
                }

                filesProcessed++;
            }
            else
            {
                logger.LogInformation("{FileName} not uploaded because the " +
                    "request exceeded the allowed {Count} of files (Err: 4)",
                    trustedFileNameForDisplay, maxAllowedFiles);
                uploadResult.ErrorCode = 4;
            }

            uploadResults.Add(uploadResult);
        }

        return new CreatedResult(resourcePath, uploadResults);
    }
}

V předchozím kódu se volá k GetRandomFileName vygenerování zabezpečeného názvu souboru. Nikdy nedůvěřujte názvu souboru poskytnutému prohlížečem, protože cyberattacker může zvolit existující název souboru, který přepíše existující soubor nebo odešle cestu, která se pokusí zapsat mimo aplikaci.

Serverová aplikace musí registrovat služby kontroleru a koncové body kontroleru mapování. Další informace najdete v tématu Směrování na akce kontroleru v ASP.NET Core. Doporučujeme přidání služeb kontroleru AddControllersWithViews k automatickému zmírnění útoků XSRF/CSRF (Cross-Site Request Forgery) pro ověřené uživatele. Pokud použijete pouze AddControllers, antiforgery se neaktivuje automaticky. Další informace najdete v tématu Směrování na akce kontroleru v ASP.NET Core.

Konfigurace požadavků mezi zdroji (CORS) na serveru se vyžaduje pro streamování požadavků při hostování serveru v jiném původu a předběžný požadavek je vždy proveden klientem. V konfiguračním souboru služby na serveru Program (projekt serveru pro Blazor Web App aplikaci nebo webové rozhraní API backendového serveru Blazor WebAssembly) je následující výchozí zásada CORS vhodná pro testování s příklady v tomto článku. Klient provede místní požadavek z portu 5003. Změňte číslo portu tak, aby odpovídalo portu klientské aplikace, který používáte:

Nakonfigurujte požadavky mezi zdroji (CORS) na serveru. V konfiguračním souboru služby na serveru Program (projekt serveru pro Blazor Web App aplikaci nebo webové rozhraní API backendového serveru Blazor WebAssembly) je následující výchozí zásada CORS vhodná pro testování s příklady v tomto článku. Klient provede místní požadavek z portu 5003. Změňte číslo portu tak, aby odpovídalo portu klientské aplikace, který používáte:

Nakonfigurujte požadavky mezi zdroji (CORS) na serveru. V konfiguračním souboru webového API backendového serveru je následující výchozí politika CORS vhodná pro testování s příklady v tomto článku. Klient provede místní požadavek z portu 5003. Změňte číslo portu tak, aby odpovídalo portu klientské aplikace, který používáte:

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("https://localhost:5003")
                  .AllowAnyMethod()
                  .AllowAnyHeader();
        });
});

Po volání UseHttpsRedirection v Program souboru zavolejte UseCors k přidání middleware CORS:

app.UseCors();

Další informace najdete v tématu Povolení žádostí mezi zdroji (CORS) v ASP.NET Core.

Nakonfigurujte maximální velikost textu požadavku serveru a omezení délky textu s více částmi, pokud omezení omezuje velikost nahrávání.

Kestrel Pro server nastavte MaxRequestBodySize (výchozí: 30 000 000 bajtů) a FormOptions.MultipartBodyLengthLimit (výchozí: 134 217 728 bajtů). Nastavte proměnnou maxFileSize v komponentě a kontroleru na stejnou hodnotu.

V následující konfiguraci souboru Program (serverového projektu Kestrel nebo webového API back-endového serveru aplikace Blazor Web App) je zástupný symbol Blazor WebAssembly limitem v bajtech.

using Microsoft.AspNetCore.Http.Features;

...

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxRequestBodySize = {LIMIT};
});

builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = {LIMIT};
});

Zrušení nahrání souboru

Komponenta pro nahrání souboru může zjistit, kdy uživatel zrušil nahrávání, pomocí funkce CancellationToken při volání do nebo IBrowserFile.OpenReadStreamStreamReader.ReadAsync.

Vytvořte komponentu CancellationTokenSourceInputFile . Na začátku OnInputFileChange metody zkontrolujte, jestli probíhá předchozí nahrání.

Pokud probíhá nahrávání souboru:

Nahrání souborů na straně serveru s průběhem

Následující příklad ukazuje, jak nahrát soubory v aplikaci na straně serveru s průběhem nahrávání zobrazeným uživateli.

Použití následujícího příkladu v testovací aplikaci:

  • Vytvořte složku pro ukládání nahraných souborů pro Development prostředí: Development/unsafe_uploads.
  • Nakonfigurujte maximální velikost souboru (maxFileSizev následujícím příkladu 15 kB) a maximální počet povolených souborů (maxAllowedFiles3 v následujícím příkladu).
  • V případě potřeby nastavte vyrovnávací paměť na jinou hodnotu (10 kB v následujícím příkladu) pro zvýšení členitosti probíhajícího hlášení. Nedoporučujeme používat vyrovnávací paměť větší než 30 kB kvůli problémům s výkonem a zabezpečením.

Upozorňující

Příklad ukládá soubory bez skenování jejich obsahu a pokyny v tomto článku nebere v úvahu další osvědčené postupy zabezpečení pro nahrané soubory. V přípravných a produkčních systémech zakažte oprávnění ke spuštění složky pro nahrávání a skenování souborů pomocí rozhraní API antivirového nebo antimalwarového skeneru okamžitě po nahrání. Další informace najdete v tématu Nahrání souborů v 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 = [];
    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;
    }
}

Další informace najdete v následujících zdrojích rozhraní API:

  • FileStream: Poskytuje Stream soubor, který podporuje synchronní i asynchronní operace čtení a zápisu.
  • FileStream.ReadAsync: Předchozí FileUpload3 komponenta čte stream asynchronně pomocí ReadAsync. Čtení streamu synchronně s Read komponentami se nepodporuje Razor .

Streamy souborů

Při interaktivitě serveru se data souboru streamují přes SignalR připojení k kódu .NET na serveru při čtení souboru.

RemoteBrowserFileStreamOptions umožňuje konfigurovat charakteristiky nahrávání souborů.

U komponenty s vykreslenou webAssembly se data souboru streamují přímo do kódu .NET v prohlížeči.

Nahrání náhledu obrázku

Pokud chcete zobrazit náhled obrázku pro nahrávání obrázků, začněte přidáním InputFile komponenty s odkazem na komponentu a obslužnou rutinou OnChange :

<InputFile @ref="inputFile" OnChange="ShowPreview" />

Přidejte prvek obrázku s odkazem na element, který slouží jako zástupný symbol pro náhled obrázku:

<img @ref="previewImageElem" />

Přidejte přidružené odkazy:

@code {
    private InputFile? inputFile;
    private ElementReference previewImageElem;
}

V JavaScriptu přidejte funkci volanou s kódem HTML input a img elementem, který provádí následující akce:

  • Extrahuje vybraný soubor.
  • Vytvoří adresu URL objektu pomocí createObjectURL.
  • Nastaví naslouchací proces události tak, aby po načtení obrázku odvolal adresu URL revokeObjectURL objektu, takže paměť se nevracela.
  • Nastaví zdroj elementu img tak, aby zobrazil obrázek.
window.previewImage = (inputElem, imgElem) => {
  const url = URL.createObjectURL(inputElem.files[0]);
  imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
  imgElem.src = url;
}

Nakonec pomocí vložené IJSRuntime rutiny přidejte obslužnou rutinu OnChange , která volá funkci JavaScriptu:

@inject IJSRuntime JS

...

@code {
    ...

    private async Task ShowPreview() => await JS.InvokeVoidAsync(
        "previewImage", inputFile!.Element, previewImageElem);
}

Předchozí příklad slouží k nahrání jednoho obrázku. Tento přístup je možné rozšířit tak, aby podporoval multiple obrázky.

Následující FileUpload4 komponenta ukazuje úplný příklad.

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

Ukládání malých souborů přímo do databáze pomocí EF Core

Mnoho aplikací ASP.NET Core používá ke správě databázových operací EF Core Entity Framework Core (). Běžným požadavkem je ukládání miniatur a avatarů přímo do databáze. Tato část ukazuje obecný přístup, který je možné dále rozšířit pro produkční aplikace.

Následující vzor:

  • Je založená na Blazor videobázové výukové aplikaci.
  • Lze rozšířit o další kód pro zpětnou vazbu k ověření velikosti souboru a typu obsahu .
  • Způsobuje snížení výkonu a riziko DoS. Při čtení jakéhokoli souboru do paměti pečlivě zvažte riziko a zvažte alternativní přístupy, zejména u větších souborů. Mezi alternativní přístupy patří ukládání souborů přímo na disk nebo službu třetí strany pro antivirovou/antimalwarovou kontrolu, další zpracování a obsluhu klientů.

Aby následující příklad fungoval v prostředí Blazor Web App (.NET 8 nebo novější), musí komponenta přijmout interaktivní režim vykreslování (například @rendermode InteractiveServer) pro volání HandleSelectedThumbnail změny InputFile souboru komponenty (OnChange parametr/událost). Blazor Server komponenty aplikace jsou vždy interaktivní a nevyžadují režim vykreslování.

V následujícím příkladu se malá miniatura (<= 100 kB) v IBrowserFile uloží do databáze s EF Core. Pokud uživatel pro komponentu InputFile nevybere soubor, uloží se do databáze výchozí miniatura.

Výchozí miniatura (default-thumbnail.jpg) je v kořenovém adresáři projektu s nastavením Kopírovat do výstupního adresáře nastavení Kopírovat, pokud je novější:

výchozí obecný obrázek miniatury

Model Movie (Movie.cs) má vlastnost (Thumbnail) pro uložení dat miniatur obrázku:

[Column(TypeName = "varbinary(MAX)")]
public byte[]? Thumbnail { get; set; }

Image data jsou uložena jako bajty v databázi jako varbinary(MAX). Aplikace kóduje bajty pomocí base-64 pro zobrazení, protože data kódovaná pomocí base-64 jsou zhruba o třetinu větší než nezpracované bajty obrázku, takže data obrázku ve formátu base-64 vyžadují další úložiště v databázi a snižují výkon operací čtení a zápisu databáze.

Komponenty zobrazující miniaturu předávají data obrázků do atributu img značky src ve formátu JPEG s kódováním BASE-64:

<img src="data:image/jpeg;base64,@Convert.ToBase64String(movie.Thumbnail)" 
    alt="User thumbnail" />

V následující Create komponentě probíhá nahrávání obrázku. Příklady můžete dále vylepšit použitím vlastního ověřování typu a velikosti souboru využitím přístupů v ASP.NET Core Blazor ověřování formulářů. Pokud chcete zobrazit úplnou komponentu Create bez kódu pro nahrání miniatur v následujícím příkladu, podívejte se na ukázkovou aplikaci BlazorWebAppMovies v úložišti GitHub Blazor ukázky.

Components/Pages/MoviePages/Create.razor:

@page "/movies/create"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using BlazorWebAppMovies.Models
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory
@inject NavigationManager NavigationManager

...

<div class="row">
    <div class="col-md-4">
        <EditForm method="post" Model="Movie" OnValidSubmit="AddMovie" 
            FormName="create" Enhance>
            <DataAnnotationsValidator />
            <ValidationSummary class="text-danger" role="alert"/>

            ...

            <div class="mb-3">
                <label for="thumbnail" class="form-label">Thumbnail:</label>
                <InputFile id="thumbnail" OnChange="HandleSelectedThumbnail" 
                    class="form-control" />
            </div>
            <button type="submit" class="btn btn-primary">Create</button>
        </EditForm>
    </div>
</div>

...

@code {
    private const long maxFileSize = 102400;
    private IBrowserFile? browserFile;

    [SupplyParameterFromForm]
    private Movie Movie { get; set; } = new();

    private void HandleSelectedThumbnail(InputFileChangeEventArgs e)
    {
        browserFile = e.File;
    }

    private async Task AddMovie()
    {
        using var context = DbFactory.CreateDbContext();

        if (browserFile?.Size > 0 && browserFile?.Size <= maxFileSize)
        {
            using var memoryStream = new MemoryStream();
            await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);

            Movie.Thumbnail = memoryStream.ToArray();
        }
        else
        {
            Movie.Thumbnail = File.ReadAllBytes(
                $"{AppDomain.CurrentDomain.BaseDirectory}default_thumbnail.jpg");
        }

        context.Movie.Add(Movie);
        await context.SaveChangesAsync();
        NavigationManager.NavigateTo("/movies");
    }
}

Stejný přístup by byl přijat v komponentě Edit s interaktivním režimem vykreslování, pokud by uživatelé mohli upravovat miniaturu videa.

Nahrání souborů do externí služby

Místo nahrání bajtů souborů a na serveru aplikace, který přijímá nahrané soubory, můžou klienti přímo nahrávat soubory do externí služby. Aplikace může bezpečně zpracovávat soubory z externí služby na vyžádání. Tento přístup zpevňuje aplikaci a její server před škodlivými útoky a potenciálními problémy s výkonem.

Zvažte přístup, který používá službu Azure Files, Azure Blob Storage nebo službu třetí strany s následujícími potenciálními výhodami:

Další informace o službě Azure Blob Storage a Azure Files najdete v dokumentaci ke službě Azure Storage.

Omezení velikosti zpráv na straně SignalR serveru

Nahrávání souborů může selhat i před jejich spuštěním, když Blazor načte data o souborech, které překračují maximální SignalR velikost zprávy.

SignalR definuje limit velikosti zprávy, který se vztahuje na všechny zprávy Blazor přijímá, a InputFile komponenta streamuje soubory na server ve zprávách, které respektují nakonfigurovaný limit. První zpráva, která označuje sadu souborů k nahrání, se ale odešle jako jedinečná jedna zpráva. Velikost první zprávy může překročit SignalR limit velikosti zprávy. Problém nesouvisí s velikostí souborů, souvisí s počtem souborů.

Zaprotokolovaná chyba je podobná následující:

Chyba: Připojení bylo odpojeno s chybou Chyba: Server vrátil chybu při zavření: Připojení se ukončilo s chybou. e.log @ blazor.server.js:1

Při nahrávání souborů je dosažení limitu velikosti zprávy u první zprávy vzácné. Pokud dosáhnete limitu, aplikace může nakonfigurovat HubOptions.MaximumReceiveMessageSize větší hodnotu.

Další informace o SignalR konfiguraci a nastavení MaximumReceiveMessageSizenajdete v pokynechBlazor k ASP.NET CoreSignalR.

Maximální počet paralelních volání na nastavení centra klienta

Blazor spoléhá na MaximumParallelInvocationsPerClient hodnotu 1, což je výchozí hodnota.

Zvýšení hodnoty vede k vysoké pravděpodobnosti, že CopyTo operace vyvolává System.InvalidOperationException: 'Reading is not allowed after reader was completed.'. Další informace naleznete v tématu MaximumParallelInvocationsPerClient > 1 break file upload in Blazor Server mode (dotnet/aspnetcore #53951).

Odstraňování potíží

Řádek, který volá, vyvolá IBrowserFile.OpenReadStreamSystem.TimeoutException:

System.TimeoutException: Did not receive any data in the allotted time.

Možné příčiny:

Další materiály