ASP.NET Core Blazor-fájlok feltöltése
Jegyzet
Ez nem a cikk legújabb verziója. Kérjük, tekintse meg az aktuális, a .NET 9-es verzió kiadásához kapcsolódó cikket ebben a formában.
Figyelmeztetés
A ASP.NET Core ezen verziója már nem támogatott. További információ: .NET és .NET Core támogatási szabályzat. Az aktuális kiadást lásd ennek a cikknek a .NET 9-es verziójában.
Fontos
Ezek az információk egy olyan előzetes termékre vonatkoznak, amelyet a kereskedelmi forgalomba kerülés előtt jelentősen módosíthatnak. A Microsoft nem vállal kifejezett vagy hallgatólagos szavatosságot az itt megadott információkra vonatkozóan.
Az aktuális kiadást lásd a cikk .NET 9-es verziójában.
Ez a cikk bemutatja, hogyan tölthet fel fájlokat Blazor a InputFile összetevővel.
Fájlfeltöltések
Figyelmeztetés
Mindig kövesse a biztonsági ajánlott eljárásokat, amikor engedélyezi a felhasználóknak a fájlok feltöltését. További információért lásd: Fájlok feltöltése ASP.NET Core.
A InputFile összetevővel beolvashatja a böngészőfájl adatait a .NET-kódba. A InputFile összetevő egy HTML-<input>
típusú file
elemet jelenít meg egyetlen fájlfeltöltéshez. Adja hozzá a multiple
attribútumot, amely lehetővé teszi, hogy a felhasználó egyszerre több fájlt töltsön fel.
A fájlkijelölés nem halmozható, amikor InputFile összetevőt vagy annak mögöttes HTML <input type="file">
használ, így nem adhat hozzá fájlokat egy meglévő fájlkijelöléshez. Az összetevő mindig lecseréli a felhasználó kezdeti fájlkijelölését, így a korábbi kijelölésekből származó fájlhivatkozások nem érhetők el.
Az alábbi InputFile összetevő végrehajtja a LoadFiles
metódust, amikor a OnChange (change
) esemény bekövetkezik. Egy InputFileChangeEventArgs hozzáférést biztosít a kijelölt fájllistához és az egyes fájlok részleteihez:
<InputFile OnChange="LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
Renderelt HTML:
<input multiple="" type="file" _bl_2="">
Jegyzet
Az előző példában a <input>
elem _bl_2
attribútuma használatos Blazorbelső feldolgozásához.
Ha egy felhasználó által kiválasztott fájlból szeretne adatokat olvasni, ahol a fájl bájtjait a Stream képviseli, hívja meg a IBrowserFile.OpenReadStream függvényt a fájlon, és olvasson a visszaadott adatfolyamból. További információért lásd a(z) Fájlstreamek szakaszt.
OpenReadStream a maximális méretet a Streambájtban kényszeríti ki. Egy vagy több, 500 KB-nál nagyobb fájl olvasása kivételt eredményez. Ez a korlát megakadályozza, hogy a fejlesztők véletlenül nagy fájlokat olvasnak a memóriába. A OpenReadStreammaxAllowedSize
paramétere szükség esetén nagyobb méret megadására használható.
A kis fájl feldolgozásán kívül ne olvassa be a bejövő fájlstreamet közvetlenül a memóriába egyszerre. Például ne másolja a fájl összes bájtját egy MemoryStream jelzésű célterületre, és ne olvassa be egy bájttömbbe egyszerre a teljes adatfolyamot. Ezek a megközelítések ronthatják az alkalmazások teljesítményét, és potenciálisan Szolgáltatásmegtagadás (DoS) kockázatát, különösen a kiszolgálóoldali összetevők esetében. Ehelyett fontolja meg az alábbi módszerek valamelyikének alkalmazását:
- Másolja a streamet közvetlenül egy lemezen lévő fájlba anélkül, hogy beolvassa a memóriába. Vegye figyelembe, hogy Blazor kiszolgálón kódot végrehajtó alkalmazások nem tudják közvetlenül elérni az ügyfél fájlrendszerét.
- Fájlok feltöltése közvetlenül az ügyfélből egy külső szolgáltatásba. További információ: Fájlok feltöltése külső szolgáltatásba szakasz.
Az alábbi példákban browserFile
a IBrowserFile-et implementálja egy feltöltött fájl reprezentálására. A IBrowserFile működő implementációi a jelen cikk későbbi, fájlfeltöltési összetevőiben jelennek meg.
A OpenReadStreamhívásakor azt javasoljuk, hogy adja meg a maximálisan engedélyezett fájlméretet a maxAllowedSize
paraméterben a várt fájlméretek korlátján. Az alapértelmezett érték 500 KB. A cikk példáiban egy maxFileSize
nevű maximálisan engedélyezett fájlméret-változót vagy -konstanst használnak, de általában nem mutatják egy adott érték beállítását.
✔️ támogatott: A következő módszer ajánlott, mivel a fájl Stream közvetlenül a fogyasztóhoz kerül, egy FileStream hozza létre a fájlt a megadott elérési úton.
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(fs);
✔️ támogatott: Az alábbi módszer ajánlott a Microsoft Azure Blob Storage esetében, mert a fájl Stream közvetlenül eljut UploadBlobAsync-hoz.
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream(maxFileSize));
✔️ Csak kis méretű fájlok esetén ajánlott: A következő módszer csak kis méretű fájlok esetében ajánlott, mert a fájl Stream tartalma a memóriában MemoryStream (memoryStream
), amely teljesítménybeli büntetést és DoS- kockázatot jelent. Példáért, amely bemutatja ennek a technikának a használatát a miniatűr kép IBrowserFile adatbázisba történő mentéséhez Entity Framework Core (EF Core)segítségével, lásd a jelen cikk későbbi EF Core szakaszát, amely a Kis fájlok közvetlen mentéséről szól egy adatbázisba.
using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
var smallFileByteArray = memoryStream.ToArray();
Nem ajánlott: A következő módszer NEM ajánlott, mert a fájl Stream tartalma a memóriában van String (reader
):
var reader =
await new StreamReader(browserFile.OpenReadStream(maxFileSize)).ReadToEndAsync();
Nem ajánlott: A következő módszer NEM ajánlott a Microsoft Azure Blob Storage esetében, mert a fájl Stream tartalma a MemoryStream memóriába (memoryStream
) kerül másolásra, mielőtt meghívná UploadBlobAsync:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
Egy képfájlt fogadó összetevő meghívhatja a fájl BrowserFileExtensions.RequestImageFileAsync kényelmi módszerét, hogy átméretezhesse a képadatokat a böngésző JavaScript-futtatókörnyezetében, mielőtt a rendszerképet az alkalmazásba továbbítanák. A RequestImageFileAsync hívására szolgáló használati esetek a legmegfelelőbbek Blazor WebAssembly alkalmazásokhoz.
Az Autofac vezérlés inverzió (IoC) konténer felhasználók
Ha a beépített ASP.NET Core függőséginjektálási tároló helyett a Autofac Inversion of Control (IoC) tároló t használja, állítsa DisableImplicitFromServicesParameterstrue
a kiszolgálóoldali kapcsolatcsoport-kezelő központ beállításai. További információ: FileUpload: Nem kapott adatokat a megadott időpontban (dotnet/aspnetcore
#38842).
Fájlméret olvasási és feltöltési korlátai
A HTTP/2 protokollt, HTTPS-t és CORS--t használó Chromium-alapú böngészők (például Google Chrome és Microsoft Edge) esetében az ügyféloldalon Blazor a Streams API segítségével támogatott a nagy fájlok feltöltése streaming kérésekhasználatával.
Chromium böngésző, HTTP/2 protokoll vagy HTTPS nélkül az ügyféloldali Blazor beolvassa a fájl bájtjait egyetlen JavaScript-tömbpufferbe, amikor az adatokat JavaScriptről C#-ra alakítja, amely 2 GB-ra vagy az eszköz rendelkezésre álló memóriájára korlátozódik. A nagy fájlfeltöltések sikertelenek lehetnek az ügyféloldali feltöltések esetében a InputFile összetevő használatával.
Az ügyféloldali Blazor beolvassa a fájl bájtjait egyetlen JavaScript-tömbpufferbe, amikor az adatokat JavaScriptről C#-ra alakítja, amely legfeljebb 2 GB- vagy az eszköz rendelkezésre álló memóriájára korlátozódik. A nagy fájlfeltöltések sikertelenek lehetnek az ügyféloldali feltöltések esetében a InputFile összetevő használatával. Javasoljuk, hogy kérések streamelési ASP.NET Core 9.0-s vagy újabb verziójával.
Biztonsági szempontok
Kerülni kell a IBrowserFile.Size
használatát a fájlméretkorlátok esetében
Ne használjon IBrowserFile.Size a fájlméret korlátozásához. A nem biztonságos ügyfél által megadott fájlméret helyett explicit módon adja meg a maximális fájlméretet. Az alábbi példa a maxFileSize
hozzárendelt maximális fájlméretet használja:
- var fileContent = new StreamContent(file.OpenReadStream(file.Size));
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Fájlnév biztonsága
Soha ne használjon ügyfél által megadott fájlnevet a fájlok fizikai tárolóba való mentéséhez. Hozzon létre egy biztonságos fájlnevet a fájlhoz Path.GetRandomFileName() vagy Path.GetTempFileName() használatával egy teljes elérési út létrehozásához (beleértve a fájlnevet is) az ideiglenes tároláshoz.
Razor automatikusan HTML kódolja a tulajdonságértékeket a megjelenítéshez. A következő kód biztonságosan használható:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
A Razor-n kívül mindig a HtmlEncode-et használja a fájlnevek biztonságos kódolásához a felhasználói kéréshez képest.
Számos implementációnak tartalmaznia kell a fájl meglétének ellenőrzését; ellenkező esetben a fájlt felülírja egy azonos nevű fájl. Adjon meg további logikát az alkalmazás specifikációinak való megfeleléshez.
Példák
Az alábbi példák több fájlfeltöltést mutatnak be egy összetevőben. InputFileChangeEventArgs.GetMultipleFiles több fájl olvasását teszi lehetővé. A fájlok maximális számának meghatározásával akadályozza meg, hogy egy rosszindulatú felhasználó az alkalmazás által vártnál több fájlt töltsön fel. InputFileChangeEventArgs.File lehetővé teszi az első és egyetlen fájl olvasását, ha a fájlfeltöltés nem támogat több fájlt.
InputFileChangeEventArgs a Microsoft.AspNetCore.Components.Forms névtérben található, amely általában az alkalmazás _Imports.razor
fájljának egyik névtere. Ha a névtér megtalálható a _Imports.razor
fájlban, az API-tagok számára hozzáférést biztosít az alkalmazás összetevőihez.
A _Imports.razor
fájlban lévő névterek nem lesznek alkalmazva C#-fájlokra (.cs
). A C#-fájlok explicit using
utasítást igényelnek az osztályfájl tetején:
using Microsoft.AspNetCore.Components.Forms;
A fájlfeltöltési összetevők teszteléséhez bármilyen méretű tesztfájlt létrehozhat PowerShell-:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
Az előző parancsban:
- A
{SIZE}
helyőrző a fájl mérete bájtban (például 2 MB-os fájl esetén2097152
). - A
{PATH}
helyőrző az elérési utat és a fájlkiterjesztéssel rendelkező fájlt jelöli (példáulD:/test_files/testfile2MB.txt
).
Példa kiszolgálóoldali fájlfeltöltésre
A következő kód használatához hozzon létre egy Development/unsafe_uploads
mappát a Development
környezetben futó alkalmazás gyökerénél.
Mivel a példa az alkalmazás környezetét használja a fájlok mentési útvonalának részeként, további mappákra van szükség, ha más környezeteket használnak teszteléskor és éles környezetben. Hozzon létre például egy Staging/unsafe_uploads
mappát a Staging
környezethez. Hozzon létre egy Production/unsafe_uploads
mappát a Production
környezethez.
Figyelmeztetés
A példa anélkül menti a fájlokat, hogy beolvasta volna a tartalmát, és a jelen cikkben szereplő útmutatás nem veszi figyelembe a feltöltött fájlok biztonsági ajánlott eljárásait. Teszt- és éles rendszereken tiltsa le a feltöltési mappára vonatkozó végrehajtási engedélyeket, és a feltöltés után azonnal vizsgálja meg a fájlokat egy víruskereső és kártevőszűrő API-val. További információ: Fájlok feltöltése az 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;
}
}
Ügyféloldali fájlfeltöltési példa
Az alábbi példa feldolgozza a fájlbájtokat, és nem küld fájlokat az alkalmazáson kívüli célhelyre. Példa egy Razor összetevőre, amely egy fájlt küld egy kiszolgálónak vagy szolgáltatásnak, tekintse meg a következő szakaszokat:
- Fájlok feltöltése kiszolgálóra ügyféloldali rendereléssel (CSR)
- Fájlok feltöltése külső szolgáltatásba
Az összetevő feltételezi, hogy az Interaktív WebAssembly renderelési mód (InteractiveWebAssembly
) öröklődik egy szülőösszetevőtől, vagy globálisan alkalmazva van az alkalmazásra.
@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 tulajdonságokként visszaadja a böngésző által által közzétett metaadatokat. Használja ezt a metaadatokat az előzetes ellenőrzéshez.
Soha ne bízzon meg az előző tulajdonságok értékeiben, különösen a felhasználói felületen való megjelenítéshez használt Name tulajdonságban. A felhasználó által megadott adatok kezelése jelentős biztonsági kockázatot jelent az alkalmazásra, a kiszolgálóra és a hálózatra nézve. További információ, lásd: Fájlok feltöltése ASP.NET Core.
Fájlok feltöltése kiszolgálóoldali rendereléssel
Ez a szakasz az Blazor Web Apps vagy Blazor Server alkalmazások interaktív kiszolgálóösszetevőire vonatkozik.
Az alábbi példa bemutatja, hogyan tölthet fel fájlokat egy kiszolgálóoldali alkalmazásból egy háttérbeli webes API-vezérlőbe egy külön alkalmazásba, esetleg egy külön kiszolgálóra.
A kiszolgálóoldali alkalmazás Program
fájljában adjon hozzá IHttpClientFactory és kapcsolódó szolgáltatásokat, amelyek lehetővé teszik, hogy az alkalmazás HttpClient példányokat hozzon létre:
builder.Services.AddHttpClient();
További információért lásd: HTTP-kérések létrehozása ASP.NET Core-ban az IHttpClientFactory használatával.
Az ebben a szakaszban található példák:
- A webes API a következő URL-címen fut:
https://localhost:5001
- A kiszolgálóoldali alkalmazás a következő URL-címen fut:
https://localhost:5003
Teszteléshez az előző URL-címek a projektek Properties/launchSettings.json
fájljaiban vannak konfigurálva.
Az alábbi UploadResult
osztály egy feltöltött fájl eredményét tartja karban. Ha egy fájl nem tölthető fel a kiszolgálón, a rendszer hibakódot ad vissza a ErrorCode
a felhasználó számára való megjelenítéshez. A rendszer minden fájlhoz létrehoz egy biztonságos fájlnevet a kiszolgálón, és visszaadja az ügyfélnek a StoredFileName
megjelenítés céljából. A fájlok az ügyfél és a kiszolgáló között a nem biztonságos/nem megbízható fájlnév használatával vannak kulcsra osztva a 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; }
}
A termelési környezetben működő alkalmazások biztonsági legjobb gyakorlata az, hogy ne küldjünk olyan hibaüzeneteket a klienseknek, amelyek bizalmas információkat fedhetnek fel egy alkalmazásról, szerverről vagy hálózatról. Ha részletes hibaüzeneteket ad meg, azzal segítheti a rosszindulatú felhasználókat az alkalmazások, kiszolgálók vagy hálózatok elleni támadások kidolgozásában. Az ebben a szakaszban szereplő példakód csak egy hibakódszámot (int
) küld vissza az összetevő ügyféloldali megjelenítéséhez, ha kiszolgálóoldali hiba történik. Ha egy felhasználónak segítségre van szüksége egy fájlfeltöltéssel kapcsolatban, akkor megadja a hibakódot a támogatási személyzetnek a támogatási jegy megoldásához, anélkül, hogy tudná a hiba pontos okát.
Az alábbi LazyBrowserFileStream
osztály olyan egyéni streamtípust határoz meg, amely lazán meghívja OpenReadStream a stream első bájtjainak kérése előtt. A stream csak akkor kerül továbbításra a böngészőből a szerverre, amíg meg nem kezdődik a stream olvasása .NET-ben.
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();
}
Az alábbi FileUpload2
összetevő:
- Lehetővé teszi, hogy a felhasználók fájlokat töltsenek fel az ügyfélből.
- Megjeleníti az ügyfél által a felhasználói felületen megadott nem megbízható/nem biztonságos fájlnevet. A nem megbízható/nem biztonságos fájlnevet a Razor automatikusan HTML-kódolja a felhasználói felületen való biztonságos megjelenítés érdekében.
Figyelmeztetés
Ne bízzon meg az ügyfelek által megadott fájlnevekben, a következőhöz:
- Mentse a fájlt egy fájlrendszerbe vagy szolgáltatásba.
- Olyan felhasználói felületeken jelenik meg, amelyek nem kódolják automatikusan vagy fejlesztői kóddal a fájlneveket.
A fájlok kiszolgálóra való feltöltésével kapcsolatos biztonsági szempontokról a Fájlok feltöltése ASP.NET Corecímű témakörben talál további információt.
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();
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; }
}
}
Ha az összetevő egyszerre korlátozza a fájl feltöltését egyetlen fájlba, vagy ha az összetevő csak ügyféloldali renderelést (CSR, InteractiveWebAssembly
) alkalmaz, az összetevő elkerülheti a LazyBrowserFileStream
használatát, és Streamhasználhat. Az alábbiakban a FileUpload2
összetevő változásait mutatjuk be:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Távolítsa el a LazyBrowserFileStream
osztályt (LazyBrowserFileStream.cs
), mert nincs használatban.
Ha az összetevő egyszerre egyetlen fájlba korlátozza a fájlok feltöltését, az összetevő elkerülheti a LazyBrowserFileStream
használatát, és használhat egy Stream. Az alábbiakban a FileUpload2
összetevő változásait mutatjuk be:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Távolítsa el a LazyBrowserFileStream
osztályt (LazyBrowserFileStream.cs
), mert nincs használatban.
A webes API-projekt alábbi vezérlője menti a feltöltött fájlokat az ügyfélből.
Fontos
A jelen szakaszban szereplő vezérlő a Blazor alkalmazástól eltérő webes API-projektben való használatra szolgál. A webes API-nak csökkentenie kell a keresztoldali kérelemhamisítási (XSRF/CSRF) támadásokat, ha a fájlfeltöltő felhasználók hitelesítve vannak.
Jegyzet
A [FromForm]
attribútummal kötési űrlapértékek natív módon nem támogatottak Minimális API-k ASP.NET Core-ban a .NET 6-ban. Ezért a következő Filesave
vezérlő példája nem konvertálható minimális API-k használatára. Az űrlapértékek minimális API-kkal való kötésének támogatása a .NET 7-ben vagy újabb ASP.NET Core-ban érhető el.
A következő kód használatához hozzon létre egy Development/unsafe_uploads
mappát a webes API-projekt gyökerénél a Development
környezetben futó alkalmazáshoz.
Mivel a példa az alkalmazás környezetét használja a fájlok mentésére szolgáló útvonal részeként, további mappák szükségesek, ha más környezeteket használnak teszteléshez és éles környezetben. Hozzon létre például egy Staging/unsafe_uploads
mappát a Staging
környezethez. Hozzon létre egy Production/unsafe_uploads
mappát a Production
környezethez.
Figyelmeztetés
A példa anélkül menti a fájlokat, hogy beolvasta volna a tartalmát, és a jelen cikkben szereplő útmutatás nem veszi figyelembe a feltöltött fájlok biztonsági ajánlott eljárásait. Előkészítési és éles rendszereken tiltsa le a feltöltési mappára vonatkozó végrehajtási engedélyeket, és közvetlenül a feltöltés után vizsgálja meg a fájlokat anti-virus/anti-malware scanner API-val. További információkért lásd: Fájlok feltöltése 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);
}
}
Az előző kódban a rendszer meghívja GetRandomFileName egy biztonságos fájlnév létrehozásához. Soha ne bízzon meg a böngésző által megadott fájlnévben, mert a kiberbűnöző választhat egy meglévő fájlnevet, amely felülír egy meglévő fájlt, vagy olyan elérési utat küld, amely az alkalmazáson kívül próbál írni.
A kiszolgálóalkalmazásnak regisztrálnia kell a kontroller szolgáltatásokat és hozzárendelnie kell a kontroller végpontokat. További információért lásd: Útvonalak vezérlő akciókhoz az ASP.NET Core-ban.
Fájlok feltöltése ügyféloldali rendereléssel (CSR) rendelkező kiszolgálóra
Ez a szakasz az ügyféloldali renderelt (CSR) összetevőkre vonatkozik Blazor Web Apps vagy Blazor WebAssembly alkalmazásokban.
Az alábbi példa bemutatja, hogyan tölthet fel fájlokat egy háttérbeli webes API-vezérlőbe egy külön alkalmazásba, esetleg egy külön kiszolgálóra egy olyan Blazor Web App összetevőjéből, amely csR-t vagy összetevőt fogad el egy Blazor WebAssembly-alkalmazásban.
A példában Chromium-alapú böngésző (például Google Chrome vagy Microsoft Edge) streamelési kér http/2 protokollal és HTTPS protokollal. Ha a kérések streamelése nem használható, Blazor kecsesen csökkenti a lekéréses API-kérések streamelése nélkül. További információ: Fájlméret olvasási és feltöltési korlátai szakasz.
Az alábbi UploadResult
osztály egy feltöltött fájl eredményét tartja karban. Ha egy fájl nem tölthető fel a kiszolgálón, a rendszer hibakódot ad vissza a ErrorCode
a felhasználó számára való megjelenítéshez. A rendszer minden fájlhoz létrehoz egy biztonságos fájlnevet a kiszolgálón, és visszaadja az ügyfélnek a StoredFileName
megjelenítés céljából. A fájlok az ügyfél és a kiszolgáló között a nem biztonságos/nem megbízható fájlnév használatával vannak kulcsra osztva a 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; }
}
Jegyzet
Az előző UploadResult
osztály megosztható az ügyfél- és kiszolgálóalapú projektek között. Amikor az ügyfél- és kiszolgálóprojektek megosztják az osztályt, adjon hozzá egy importálást az egyes projektek _Imports.razor
fájljába a megosztott projekthez. Például:
@using BlazorSample.Shared
Az alábbi FileUpload2
összetevő:
- Lehetővé teszi, hogy a felhasználók fájlokat töltsenek fel az ügyfélből.
- Megjeleníti az ügyfél által a felhasználói felületen megadott nem megbízható/nem biztonságos fájlnevet. A nem megbízható/nem biztonságos fájlnevet a Razor automatikusan HTML-kódolja a felhasználói felületen való biztonságos megjelenítés érdekében.
Az éles alkalmazások esetében a biztonsági legjobb gyakorlat az, hogy kerüljük a hibaüzenetek küldését a klienseknek, mivel ezek bizalmas információkat fedhetnek fel egy alkalmazásról, szerverről vagy hálózatról. Ha részletes hibaüzeneteket ad meg, azzal segítheti a rosszindulatú felhasználókat az alkalmazások, kiszolgálók vagy hálózatok elleni támadások kidolgozásában. Az ebben a szakaszban szereplő példakód csak egy hibakódszámot (int
) küld vissza az összetevő ügyféloldali megjelenítéséhez, ha kiszolgálóoldali hiba történik. Ha egy felhasználónak segítségre van szüksége egy fájlfeltöltéshez, akkor megadja a hibakódot a támogatási személyzetnek, hogy segítsenek a támogatási jegy megoldásában, anélkül, hogy tudná a hiba pontos okát.
Figyelmeztetés
Ne bízzon meg az ügyfelek által megadott fájlnevekben ebből a célból:
- Mentse a fájlt egy fájlrendszerbe vagy szolgáltatásba.
- Olyan felhasználói felületeken jelenik meg, amelyek nem kódolják automatikusan vagy fejlesztői kóddal a fájlneveket.
A fájlok kiszolgálóra való feltöltésével kapcsolatos biztonsági szempontokról a Fájlok feltöltése ASP.NET Corecímű témakörben talál további információt.
A Blazor Web App kiszolgálóprojektben adja hozzá a IHttpClientFactory és a kapcsolódó szolgáltatásokat a projekt Program
fájljában:
builder.Services.AddHttpClient();
A HttpClient szolgáltatásokat hozzá kell adni a kiszolgálóprojekthez, mert az ügyféloldali összetevő előrerendelt a kiszolgálón. Ha tiltsa le a következő összetevő előrendelését, akkor nem kell biztosítania a kiszolgálóprojekt HttpClient szolgáltatásait, és nem kell hozzáadnia az előző sort a kiszolgálóprojekthez.
További információ a HttpClient szolgáltatások ASP.NET Core-alkalmazásokhoz való hozzáadásáról: HTTP-kérések készítése az IHttpClientFactory használatával a ASP.NET Core.
A Blazor Web App ügyfélprojektjének (.Client
) is regisztrálnia kell egy HttpClient a HTTP POST-kérelmekhez egy háttérbeli webes API-vezérlőn. Erősítse meg vagy adja hozzá a következőket az ügyfélprojekt Program
fájlhoz:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
Az előző példa az alapcímet builder.HostEnvironment.BaseAddress
(IWebAssemblyHostEnvironment.BaseAddress) értékre állítja, amely lekéri az alkalmazás alapcímét, és általában a gazdagépoldal <base>
címkéjének href
értékéből kerül meghatározásra. Ha külső webes API-t hív meg, állítsa az URI-t a webes API alapcímére.
Egy önálló Blazor WebAssembly alkalmazás, amely fájlokat tölt fel egy különálló kiszolgáló webes API-jára, vagy egy HttpClient
nevű-et használ, vagy beállítja az alapértelmezett HttpClient szolgáltatásregisztrációt, hogy a webes API végpontjára mutasson. Az alábbi példában, ahol a webes API helyileg, az 5001-es porton van üzemeltetve, az alapcím https://localhost:5001
:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri("https://localhost:5001") });
Egy Blazor Web App-ban adja hozzá a Microsoft.AspNetCore.Components.WebAssembly.Http névteret az összetevő irányelveihez.
@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)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
request.SetBrowserRequestStreamingEnabled(true);
request.Content = content;
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)
{
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; }
}
}
A kiszolgálóoldali projekt alábbi vezérlője menti a feltöltött fájlokat az ügyfélből.
Jegyzet
A [FromForm]
attribútummal kötési űrlapértékek natív módon nem támogatottak Minimális API-k ASP.NET Core-ban a .NET 6-ban. Ezért a következő Filesave
vezérlő példája nem konvertálható minimális API-k használatára. Az űrlapértékek minimális API-kkal való kötésének támogatása a .NET 7-ben vagy újabb ASP.NET Core-ban érhető el.
A következő kód használatához hozzon létre egy Development/unsafe_uploads
mappát a kiszolgálóoldali projekt gyökérmappájában a Development
környezetben futó alkalmazáshoz.
Mivel a példa az alkalmazás környezetét használja a fájlok mentési útvonalának részeként, további mappákra van szükség, ha más környezeteket használnak teszteléshez és éles használatra. Hozzon létre például egy Staging/unsafe_uploads
mappát a Staging
környezethez. Hozzon létre egy Production/unsafe_uploads
mappát a Production
környezethez.
Figyelmeztetés
A példa anélkül menti a fájlokat, hogy beolvasta volna a tartalmát, és a jelen cikkben szereplő útmutatás nem veszi figyelembe a feltöltött fájlok biztonsági ajánlott eljárásait. Előkészítési és éles rendszereken tiltsa le a feltöltési mappára vonatkozó végrehajtási engedélyeket, és közvetlenül a feltöltés után vizsgálja meg a fájlokat egy vírusirtó/malware-ellenőrző API-val. További információkért lásd: Fájlok feltöltése ASP.NET Core.
Az alábbi példában egy üzemeltetett Blazor WebAssembly-alkalmazáshoz, vagy ha a megosztott projekt a UploadResult
osztály megadására szolgál, adja hozzá a megosztott projekt névterét:
using BlazorSample.Shared;
Javasoljuk, hogy használjon névteret a következő vezérlőhöz (például: 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);
}
}
Az előző kódban a rendszer meghívja GetRandomFileName egy biztonságos fájlnév létrehozásához. Soha ne bízzon meg a böngésző által megadott fájlnévben, mert a kiberbűnöző választhat egy meglévő fájlnevet, amely felülír egy meglévő fájlt, vagy olyan elérési utat küld, amely az alkalmazáson kívül próbál írni.
A kiszolgálóalkalmazásnak regisztrálnia kell a vezérlőszolgáltatásokat és leképeznie kell a vezérlő végpontokat. További információ: Útvonalak az ASP.NET Core vezérlő műveletekhez. Javasoljuk, hogy AddControllersWithViews vezérlőszolgáltatásokat adjon hozzá, hogy automatikusan mérsékelje a helyek közötti kérelemhamisítást (XSRF/CSRF) a hitelesített felhasználók. Ha csak a AddControllers-at használja, az antiforgery nem lesz automatikusan engedélyezve. További információért lásd: Útválasztás az ASP.NET Core vezérlőműveleteihez.
A kiszolgálón a kereszt-eredetű kérések (CORS) konfigurációja szükséges a kérések streameléséhez, ha a kiszolgáló egy másik eredetnél van üzemeltetve, és az ügyfél mindig küld egy előzetes ellenőrző kérést. A kiszolgáló Program
fájljának szolgáltatáskonfigurációjában (egy Blazor Web App kiszolgálóprojektje vagy egy Blazor WebAssembly-alkalmazás háttérkiszolgálói webes API-ja) az alábbi alapértelmezett CORS-szabályzat alkalmas a jelen cikkben szereplő példákkal való tesztelésre. Az ügyfél a helyi kérést az 5003-as portról intézi. Módosítsa a portszámot a használt ügyfélalkalmazás portjának megfelelőre:
A kiszolgálón konfigurálja a cross-origin kéréseket (CORS). A kiszolgáló Program
fájljának szolgáltatáskonfigurációjában (egy Blazor Web App kiszolgálóprojektje vagy egy Blazor WebAssembly-alkalmazás háttérkiszolgálói webes API-ja) az alábbi alapértelmezett CORS-szabályzat alkalmas a jelen cikkben szereplő példákkal való tesztelésre. Az ügyfél a helyi kérést az 5003-as portról intézi. Módosítsa a portszámot a használt ügyfélalkalmazás portjának megfelelőre:
Állítsa be a kiszolgálón a kereszt-eredetű kérelmeket (CORS). A háttérkiszolgáló webes API Program
fájljának szolgáltatáskonfigurációjában az alábbi alapértelmezett CORS-szabályzat alkalmas a cikkben szereplő példákkal való tesztelésre. Az ügyfél a helyi kérést az 5003-as portról intézi. Módosítsa a portszámot a használt ügyfélalkalmazás portjának megfelelőre:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
Miután meghívta UseHttpsRedirection a Program
fájlban, hívja meg UseCors a CORS köztes szoftver hozzáadásához:
app.UseCors();
További információt a ASP.NET Core Keresztirányú kérelmek engedélyezése (CORS) című témakörben talál.
Konfigurálja a kiszolgáló maximális kérelemtörzs-méretét és többrészes törzshosszkorlátait, ha a korlátok korlátozzák a feltöltés méretét.
A Kestrel kiszolgálónál állítsa be a MaxRequestBodySize (alapértelmezett: 30 000 000 bájt) és FormOptions.MultipartBodyLengthLimit (alapértelmezett: 134 217 728 bájt). Állítsa a maxFileSize
változót az összetevőben és a vezérlőben ugyanarra az értékre.
A következő Program
fájl Kestrel konfigurációjában (egy Blazor Web App kiszolgálóprojektjében vagy egy Blazor WebAssembly-alkalmazás háttérkiszolgálói webes API-jában) a {LIMIT}
helyőrző a bájtban megadott korlát:
using Microsoft.AspNetCore.Http.Features;
...
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = {LIMIT};
});
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = {LIMIT};
});
Fájlfeltöltés megszakítása
A fájlfeltöltési összetevő képes észlelni, ha a felhasználó megszakította a feltöltést egy CancellationToken használatával, amikor behívja a IBrowserFile.OpenReadStream vagy StreamReader.ReadAsync.
Hozzon létre egy CancellationTokenSource a InputFile
összetevőhöz. A OnInputFileChange
metódus elején ellenőrizze, hogy egy korábbi feltöltés folyamatban van-e.
Ha a fájlfeltöltés folyamatban van:
- Hívja meg Cancel az előző feltöltésen.
- Hozzon létre egy új CancellationTokenSource, és adja át a CancellationTokenSource.TokenOpenReadStream-nek vagy ReadAsync-nak a következő feltöltéshez.
Fájlok feltöltése a kiszolgálón, állapotkövetéssel
Az alábbi példa bemutatja, hogyan tölthet fel fájlokat egy kiszolgálóoldali alkalmazásba a felhasználó számára megjelenített feltöltési folyamattal.
A következő példa használata tesztalkalmazásban:
-
Hozzon létre egy mappát a feltöltött fájlok mentéséhez a
Development
környezetbe:Development/unsafe_uploads
. - Konfigurálja a maximális fájlméretet (
maxFileSize
, 15 KB a következő példában) és az engedélyezett fájlok maximális számát (maxAllowedFiles
, 3 az alábbi példában). - Állítsa a puffert egy másik értékre (az alábbi példában 10 KB), ha szükséges, a folyamatjelentések részletességének növelése érdekében. Teljesítmény- és biztonsági problémák miatt nem javasoljuk a 30 KB-nál nagyobb puffer használatát.
Figyelmeztetés
A példa anélkül menti a fájlokat, hogy beolvasta volna a tartalmát, és a jelen cikkben szereplő útmutatás nem veszi figyelembe a feltöltött fájlok biztonsági ajánlott eljárásait. Előkészítési és éles rendszereken tiltsa le a végrehajtási engedélyeket a feltöltési mappán, és közvetlenül a feltöltés után azonnal vizsgálja át a fájlokat egy anti-virus/anti-malware scanner API segítségével. További információért lásd: Fájlok feltöltése 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;
}
}
További információkért tekintse meg a következő API-erőforrásokat:
- FileStream: Egy fájl számára biztosít Stream-t, amely támogatja a szinkron és az aszinkron olvasási és írási műveleteket.
-
FileStream.ReadAsync: Az előző
FileUpload3
összetevő aszinkron módon olvassa be a streamet ReadAsync. A stream szinkron olvasása Read nem támogatott Razor összetevőkben.
Fájl adatfolyamok
A kiszolgáló interaktivitása esetén a fájladatok a fájl olvasása közben a SignalR kapcsolaton keresztül lesznek továbbítva a kiszolgálón lévő .NET-kódba.
RemoteBrowserFileStreamOptions lehetővé teszi a fájlfeltöltési jellemzők konfigurálását.
WebAssembly-renderelt összetevő esetén a fájladatok közvetlenül a böngészőben lévő .NET-kódba kerülnek.
Kép feltöltése – előzetes verzió
A képek feltöltésének előnézetéhez először adjon hozzá egy InputFile
összetevőt, amely tartalmaz egy összetevőreferenciát, és egy OnChange
kezelőt.
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Adjon hozzá egy elemhivatkozásitartalmazó képelemet, amely a kép előnézetének helyőrzőjeként szolgál:
<img @ref="previewImageElem" />
Adja hozzá a társított hivatkozásokat:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}
JavaScriptben adjon hozzá egy HTML-input
és img
elemet tartalmazó függvényt, amely a következőket hajtja végre:
- Kinyeri a kijelölt fájlt.
- Hozzon létre egy objektum-URL-t a
createObjectURL
-val. - Beállít egy eseményfigyelőt az objektum URL-címének visszavonására a kép betöltése után
revokeObjectURL
, hogy a memória ne szivárogjon ki. - Beállítja a
img
elem forrását a kép megjelenítésére.
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
}
Végül használjon egy injektált IJSRuntime a JavaScript-függvényt meghívó OnChange
kezelő hozzáadásához:
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
Az előző példa egyetlen kép feltöltésére használható. A módszer kibővíthető multiple
képek támogatásához.
Az alábbi FileUpload4
összetevő a teljes példát mutatja.
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);
}
Kis méretű fájlok mentése közvetlenül egy adatbázisba EF Core
Számos ASP.NET Core-alkalmazás Entity Framework Core (EF Core) használ az adatbázis-műveletek kezeléséhez. A miniatűrök és avatarok közvetlenül az adatbázisba való mentése gyakori követelmény. Ez a szakasz egy általános megközelítést mutat be, amely tovább fejleszthető a gyártási alkalmazások számára.
A következő minta:
- A Blazor filmadatbázis oktatóalkalmazásán alapul,.
- További kódokkal bővíthető a fájlméret és a tartalomtípus érvényesítési visszajelzése .
- Teljesítménybírságot von maga után, és DoS- kockázatot. Gondosan mérlegelje a kockázatot, amikor bármilyen fájlt beolvas a memóriába, és fontolja meg az alternatív módszereket, különösen a nagyobb fájlok esetében. Az alternatív módszerek közé tartozik a fájlok mentése közvetlenül a lemezre vagy egy külső szolgáltatásba a víruskereső/kártevőirtó ellenőrzésekhez, a további feldolgozáshoz és az ügyfelek kiszolgálásához.
Ahhoz, hogy az alábbi példa egy Blazor Web App (ASP.NET Core 8.0 vagy újabb verzióban) működjön, az összetevőnek interaktív renderelési módot kell alkalmaznia (például @rendermode InteractiveServer
) egy HandleSelectedThumbnail
InputFile
összetevőfájl módosításának meghívásához (OnChange
paraméter/esemény).
Blazor Server alkalmazásösszetevők mindig interaktívak, és nem igényelnek renderelési módot.
Az alábbi példában egy IBrowserFile egy kis miniatűrje (<= 100 KB) lesz mentve egy EF Coretartalmazó adatbázisba. Ha a felhasználó nem választ ki egy fájlt a InputFile
összetevőhöz, a rendszer egy alapértelmezett miniatűrt ment az adatbázisba.
Az alapértelmezett miniatűr (default-thumbnail.jpg
) a projekt gyökerénél található, Másolás a kimeneti könyvtárba beállítással, Másolás, ha újabb:
A Movie
modell (Movie.cs
) rendelkezik egy tulajdonság (Thumbnail
) a miniatűr képadatok tárolásához:
[Column(TypeName = "varbinary(MAX)")]
public byte[]? Thumbnail { get; set; }
A képadatokat bájtként tároljuk az adatbázisban varbinary(MAX)
. Az alkalmazás base-64 kódolja a bájtokat megjelenítésre, mivel a base-64 kódolású adatok nagyjából harmadával nagyobbak a kép nyers bájtjainál, ezért a 64-es alapszintű képadatok további adatbázis-tárolást igényelnek, és csökkentik az adatbázis olvasási/írási műveleteinek teljesítményét.
A miniatűrt megjelenítő összetevők képadatokat adnak át a img
címke src
attribútumának JPEG, base-64 kódolású adatokként:
<img src="data:image/jpeg;base64,@Convert.ToBase64String(movie.Thumbnail)"
alt="User thumbnail" />
Az alábbi Create
összetevőben a rendszer feldolgoz egy képfeltöltést. A példát tovább fejlesztheti a fájltípus és -méret egyéni érvényesítésével az ASP.NET Core Blazor űrlapérvényesítési. Ha a következő példában a miniatűrfeltöltési kód nélkül szeretné megtekinteni a teljes Create
összetevőt, tekintse meg a BlazorWebAppMovies
mintaalkalmazást a Blazor GitHub-adattárban.
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");
}
}
Ugyanezt a megközelítést alkalmazzák az Edit
összetevőben interaktív renderelési módban, ha a felhasználók szerkeszthetik a mozgókép miniatűrjét.
Fájlok feltöltése külső szolgáltatásba
Ahelyett, hogy az alkalmazás fájlfeltöltési bájtokat kezel, és az alkalmazás kiszolgálója a feltöltött fájlokat fogadja, az ügyfelek közvetlenül feltölthetnek fájlokat egy külső szolgáltatásba. Az alkalmazás igény szerint biztonságosan feldolgozhatja a külső szolgáltatás fájljait. Ez a megközelítés megkeményíti az alkalmazást és a kiszolgálót a rosszindulatú támadások és a lehetséges teljesítményproblémák ellen.
Fontolja meg a Azure Files, az Azure Blob Storage vagy egy külső szolgáltatás használatát a következő lehetséges előnyökkel:
- Fájlok feltöltése közvetlenül az ügyfélről egy külső szolgáltatásba JavaScript-ügyfélkódtár vagy REST API használatával. Az Azure például a következő ügyfélkódtárakat és API-kat kínálja:
- Engedélyezze a felhasználók feltöltését az alkalmazás (kiszolgálóoldali) által létrehozott, felhasználó által delegált közös hozzáférésű aláírási (SAS) jogkivonattal minden ügyfélfájl feltöltéséhez. Az Azure például a következő SAS-funkciókat kínálja:
- Biztosítson automatikus redundanciát és a fájlmegosztás biztonsági mentését.
- Kvótákkal rendelkező feltöltések korlátozása. Vegye figyelembe, hogy az Azure Blob Storage kvótái a fiók szintjén vannak megadva, nem pedig a tároló szintjén. Az Azure Files-kvóták azonban a fájlmegosztás szintjén vannak, és jobban szabályozhatják a feltöltési korlátokat. További információkért tekintse meg a lista korábbi csatolt Azure-dokumentumait.
- Fájlok védelme kiszolgálóoldali titkosítással (SSE).
További információ az Azure Blob Storage-ról és az Azure Filesról: Azure Storage dokumentációja.
Kiszolgálóoldali SignalR üzenet méretkorlátja
A fájlfeltöltések még a kezdés előtt meghiúsulhatnak, ha Blazor a maximális SignalR üzenetméretet meghaladó fájlok adatait kéri le.
SignalR definiál egy üzenetméretkorlátot, amely minden Blazor kapott üzenetre vonatkozik, és a InputFile összetevő a konfigurált korlátot tiszteletben tartó üzenetekben streameli a fájlokat a kiszolgálóra. Az első üzenet azonban, amely a feltöltendő fájlok készletét jelzi, egyedi üzenetként lesz elküldve. Az első üzenet mérete meghaladhatja a SignalR üzenet méretkorlátját. A probléma nem a fájlok méretével, hanem a fájlok számával kapcsolatos.
A naplózott hiba a következőhöz hasonló:
Hiba: A kapcsolat megszakadt a következő hibával: Hiba: A kiszolgáló hibát adott vissza a bezáráskor: A kapcsolat hiba miatt lezárult." hibaüzenet jelenik meg. e.log @ blazor.server.js:1
Fájlok feltöltésekor ritkán éri el az üzenet méretkorlátját az első üzenetnél. Ha eléri a korlátot, az alkalmazás nagyobb értékkel konfigurálhatja HubOptions.MaximumReceiveMessageSize.
A SignalR konfigurálásáról és a MaximumReceiveMessageSizebeállításáról további információkat a ASP.NET Core BlazorSignalR útmutatóban találhat.
Párhuzamos meghívások maximális száma ügyfélközpont-beállításonként
Blazor attól függ, hogy a MaximumParallelInvocationsPerClient 1 értékre van állítva, ami az alapértelmezett érték.
Az érték növelése nagy valószínűséggel ahhoz vezet, hogy a CopyTo
műveletek System.InvalidOperationException: 'Reading is not allowed after reader was completed.'
-t eredményeznek. További információért lásd: MaximumParallelInvocationsPerClient > 1 leállítja a fájlfeltöltést Blazor Server módban (dotnet/aspnetcore
#53951).
Hibaelhárítás
A sor, amely a IBrowserFile.OpenReadStream-t hívja, egy System.TimeoutException-et dob:
System.TimeoutException: Did not receive any data in the allotted time.
Lehetséges okok:
A Autofac Inversion of Control (IoC) tároló használata a beépített ASP.NET Core függőséginjektálási tároló helyett a ASP.NET Core 9.0-nál korábbi verzióiban. A probléma megoldásához állítsa DisableImplicitFromServicesParameters-t
true
-re a kiszolgálóoldali áramkörkezelő központ beállításainál. További információ: FileUpload: Nem kapott adatokat a megadott időpontban (dotnet/aspnetcore
#38842).Nem olvassa el a streamet a végéig. Ez nem egy keretrendszerrel kapcsolatos probléma. A kivételt csapdába ejtheti, és tovább vizsgálhatja a helyi környezetben/hálózaton.
- A kiszolgálóoldali renderelés használata és a OpenReadStream meghívása több fájlon, mielőtt teljesen feldolgozná őket. A probléma megoldásához használja a cikk
LazyBrowserFileStream
Fájlok feltöltése kiszolgálóoldali renderelési szakaszában leírt osztályt és megközelítést.