nahrání souborů ASP.NET Core Blazor
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Tento článek vysvětluje, jak nahrát soubory do Blazor InputFile 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, zavolejte IBrowserFile.OpenReadStream soubor 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 maxAllowedSize
OpenReadStream lze v případě potřeby použít k určení větší velikosti.
Pokud potřebujete přístup k Stream bajtům souboru, použijte IBrowserFile.OpenReadStream. Vyhněte se č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 představuje browserFile
nahraný soubor a implementuje IBrowserFile. Pracovní implementace pro IBrowserFile soubory se zobrazují v komponentách pro nahrání souborů dále v tomto článku.
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().CopyToAsync(fs);
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
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()).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 MemoryStream UploadBlobAsynczkopíruje do paměti (memoryStream
):
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().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.
Uživatelé kontejneru IoC (Autofac Inversion of Control)
Pokud používáte kontejner IoC (Autofac Inversion of Control) místo integrovaného kontejneru injektáže závislostí ASP.NET Core, nastavte DisableImplicitFromServicesParameters ho v true
možnostech centra obslužných rutin okruhu na straně serveru. Další informace naleznete v tématu FileUpload: Neobdržel(a) žádná data v přiděleném čase (dotnet/aspnetcore
#38842).
Omezení čtení a nahrávání velikosti souboru
Na straně serveru nebo na straně klienta neexistuje žádný limit velikosti čtení nebo nahrávání souborů speciálně pro danou komponentu InputFile . Na straně Blazor klienta se ale bajty souboru čtou do jediné vyrovnávací paměti pole JavaScriptu při zařazování dat z JavaScriptu do jazyka C#, která je omezená na 2 GB nebo do dostupné paměti zařízení. Při nahrávání velkých souborů (> 250 MB) může dojít k selhání nahrávání na straně klienta pomocí InputFile komponenty. Další informace najdete v následujících diskuzích:
Maximální podporovaná velikost souboru pro komponentu InputFile je 2 GB. Kromě toho klientská strana Blazor čte bajty souboru do jedné vyrovnávací paměti pole JavaScriptu při zařazování dat z JavaScriptu do jazyka C#, která je omezená na 2 GB nebo na dostupnou paměť zařízení. Při nahrávání velkých souborů (> 250 MB) může dojít k selhání nahrávání na straně klienta pomocí InputFile komponenty. Další informace najdete v následujících diskuzích:
- Komponenta Blazor InputFile by měla zpracovávat bloky dat při nahrání souboru (dotnet/runtime #84685).
- Odeslání požadavku na streamování prostřednictvím obslužné rutiny http (dotnet/runtime #36634)
U velkých nahrání souborů na straně klienta, které při pokusu InputFile o použití komponenty selžou, doporučujeme místo komponenty InputFile vytvořit bloky velkých souborů s vlastní komponentou s použitím více požadavků na rozsah HTTP.
Práce je aktuálně naplánovaná na .NET 9 (na konci roku 2024), aby vyřešila omezení nahrání velikosti souboru na straně klienta.
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říklad2097152
pro soubor o velikosti 2 MB). - Zástupný
{PATH}
symbol je cesta a soubor s příponou souboru (napříkladD:/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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileName);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
loadedFiles.Add(file);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
IBrowserFile 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 = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string Name { get; set; }
}
}
Pokud komponenta omezuje nahrávání souborů do jednoho souboru najednou nebo pokud komponenta přijímá pouze interaktivní vykreslování na straně klienta (CSR), InteractiveWebAssembly
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á.
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.
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.
Blazor Web App V hlavním projektu přidejte IHttpClientFactory do souboru projektu Program
související služby:
builder.Services.AddHttpClient();
Služby HttpClient
musí být přidány do hlavního projektu, 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 hlavní aplikaci a nemusíte do hlavního projektu 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.
V horní části následující komponenty zadejte atribut Režimu vykreslování Interactive WebAssembly v Blazor Web App:
@rendermode InteractiveWebAssembly
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName);
if (result is null)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result = new();
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string Name { get; set; }
}
}
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 aktualizujte obor názvů sdíleného projektu tak, aby odpovídal sdílenému projektu, pokud sdílený projekt dodává UploadResult
třídu.
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
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.
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.OpenReadStream StreamReader.ReadAsync.
Vytvořte komponentu CancellationTokenSource InputFile
. Na začátku OnInputFileChange
metody zkontrolujte, jestli probíhá předchozí nahrání.
Pokud probíhá nahrávání souboru:
- Zavolejte Cancel na předchozí nahrání.
- Vytvořte novou CancellationTokenSource pro další nahrání a předejte ho CancellationTokenSource.Token .ReadAsyncOpenReadStream
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 (
maxFileSize
v následujícím příkladu 15 kB) a maximální počet povolených souborů (maxAllowedFiles
3 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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
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);
}
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:
- Nahrajte soubory z klienta přímo do externí služby pomocí javascriptové klientské knihovny nebo REST rozhraní API. Azure například nabízí následující klientské knihovny a rozhraní API:
- Autorizuje nahrávání uživatelů pomocí tokenu SAS (Shared Access Signature) delegovaného uživatelem vygenerovaného aplikací (na straně serveru) pro každý nahrání souboru klienta. Azure například nabízí následující funkce SAS:
- Zajištění automatické redundance a zálohování sdílených složek
- Omezte nahrávání s kvótami. Všimněte si, že kvóty služby Azure Blob Storage jsou nastavené na úrovni účtu, nikoli na úrovni kontejneru. Kvóty služby Azure Files jsou ale na úrovni sdílené složky a můžou poskytovat lepší kontrolu nad limity nahrávání. Další informace najdete v dokumentech Azure propojených dříve v tomto seznamu.
- Zabezpečení souborů pomocí šifrování na straně serveru (SSE).
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 pokynech k ASP.NET CoreBlazorSignalR.
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.OpenReadStream System.TimeoutException:
System.TimeoutException: Did not receive any data in the allotted time.
Možné příčiny:
Použití kontejneru IoC (Autofac Inversion of Control) místo integrovaného kontejneru injektáže závislostí ASP.NET Core. Pokud chcete tento problém vyřešit, nastavte DisableImplicitFromServicesParameters v
true
možnostech centra obslužných rutin okruhu na straně serveru. Další informace naleznete v tématu FileUpload: Neobdržel(a) žádná data v přiděleném čase (dotnet/aspnetcore
#38842).Nepřečtou se stream k dokončení. Nejedná se o problém s architekturou. Vychytejte výjimku a prozkoumejte ji dále v místním prostředí nebo síti.
- Použití vykreslování na straně serveru a volání OpenReadStream na více souborů před jejich přečtením k dokončení. Pokud chcete tento problém vyřešit, použijte
LazyBrowserFileStream
třídu a přístup popsaný v části Nahrání souborů na server s vykreslovací částí tohoto článku.