ASP.NET Core caricamenti di file Blazor
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Questo articolo illustra come caricare file in Blazor con il InputFile componente .
Caricamenti di file
Avviso
Seguire sempre le procedure consigliate per la sicurezza per consentire agli utenti di caricare i file. Per altre informazioni, vedere Caricare file in ASP.NET Core.
Usare il InputFile componente per leggere i dati dei file del browser nel codice .NET. Il componente InputFile esegue il rendering di un elemento HTML <input>
di tipo file
per il caricamento di singoli file. Aggiungere l'attributo multiple
per consentire all'utente di caricare più file contemporaneamente.
La selezione dei file non è cumulativa quando si usa un InputFile componente o il relativo codice HTML <input type="file">
sottostante, quindi non è possibile aggiungere file a una selezione di file esistente. Il componente sostituisce sempre la selezione iniziale del file dell'utente, quindi i riferimenti ai file delle selezioni precedenti non sono disponibili.
Il componente seguente InputFile esegue il LoadFiles
metodo quando si verifica l'evento OnChange (change
). Un InputFileChangeEventArgs oggetto consente di accedere all'elenco di file selezionato e ai dettagli su ogni file:
<InputFile OnChange="LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
HTML renderizzato
<input multiple="" type="file" _bl_2="">
Nota
Nell'esempio precedente, l'attributo dell'elemento <input>
viene usato per _bl_2
l'elaborazione Blazor interna dell'elemento.
Per leggere i dati da un file selezionato dall'utente con un Stream che rappresenta i byte del file, chiamare IBrowserFile.OpenReadStream sul file e leggere dal flusso restituito. Per altre informazioni, vedere la sezione Flussi di file .
OpenReadStream applica una dimensione massima in byte del proprio Stream. La lettura di un file o di più file di dimensioni superiori a 500 KB genera un'eccezione. Questo limite impedisce agli sviluppatori di leggere accidentalmente file di grandi dimensioni in memoria. Il maxAllowedSize
parametro di OpenReadStream può essere usato per specificare una dimensione maggiore, se necessario.
Al di fuori dell'elaborazione di un file di piccole dimensioni, evitare di leggere il flusso di file in ingresso direttamente in memoria contemporaneamente. Ad esempio, non copiare tutti i byte del file in MemoryStream o leggere l'intero flusso in una matrice di byte contemporaneamente. Questi approcci possono comportare un peggioramento delle prestazioni delle app e un potenziale rischio Denial of Service (DoS), in particolare per i componenti lato server. Prendere invece in considerazione l'adozione di uno degli approcci seguenti:
- Copiare il flusso direttamente in un file su disco senza leggerlo in memoria. Si noti che le app che Blazor eseguono codice nel server non sono in grado di accedere direttamente al file system del client.
- Caricare i file dal client direttamente in un servizio esterno. Per altre informazioni, vedere la sezione Caricare file in un servizio esterno.
Negli esempi seguenti browserFile
implementa IBrowserFile per rappresentare un file caricato. Le implementazioni di lavoro per IBrowserFile sono illustrate nei componenti di caricamento dei file più avanti in questo articolo.
Quando si chiama OpenReadStream, è consigliabile passare una dimensione massima consentita del file nel parametro maxAllowedSize
al limite delle dimensioni del file che si prevede di ricevere. Il valore predefinito è 500 KB. Gli esempi di questo articolo usano una variabile o una costante di dimensioni massime consentite denominate maxFileSize
, ma in genere non mostrano l'impostazione di un valore specifico.
Supportato: L'approccio seguente è raccomandato perché il file viene fornito direttamente all'utente, un oggetto FileStream che crea il file nel percorso specificato:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(fs);
Supportato: il seguente approccio è consigliato per Microsoft Azure Blob Storage perché il file Stream viene fornito direttamente a UploadBlobAsync:
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream(maxFileSize));
✔️ Consigliato solo per file di piccole dimensioni: L'approccio seguente è consigliato solo per i file di piccole dimensioni perché il contenuto del Stream file viene letto nella MemoryStream memoria (memoryStream
), il che comporta una penalità di prestazioni e un rischio di DoS. Per un esempio che illustra questa tecnica per salvare un'immagine di anteprima con un IBrowserFile in un database usando Entity Framework Core (EF Core), vedere la sezione Salvare file di piccole dimensioni direttamente in un database con EF Core più avanti in questo articolo.
using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
var smallFileByteArray = memoryStream.ToArray();
Non consigliato: l'approccio seguente non è consigliato perché il contenuto del Stream file viene letto in String memoria (reader
):
var reader =
await new StreamReader(browserFile.OpenReadStream(maxFileSize)).ReadToEndAsync();
Non consigliato: il seguente approccio NON è consigliato per l'archiviazione BLOB di Microsoft Azure perché il contenuto del file viene copiato in memoria (MemoryStream) prima di memoryStream
chiamare UploadBlobAsync:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
Un componente che riceve un file di immagine può chiamare il BrowserFileExtensions.RequestImageFileAsync metodo pratico sul file per ridimensionare i dati dell'immagine all'interno del runtime JavaScript del browser prima che l'immagine venga trasmessa all'app. I casi d'uso per le chiamate RequestImageFileAsync sono più appropriati per le app Blazor WebAssembly.
Utenti del contenitore Autofac Inversion of Control (IoC)
Se si utilizza il contenitore di inversione del controllo (IoC) di Autofac anziché il contenitore predefinito di inserimento delle dipendenze di ASP.NET Core, impostare DisableImplicitFromServicesParameters su true
nelle opzioni del gestore del circuito sul lato server dell'hub. Per altre informazioni, vedere FileUpload: Non sono stati ricevuti dati nel tempo assegnato (dotnet/aspnetcore
#38842).
Limiti di lettura e caricamento dei file
Per i browser basati su Chromium (ad esempio, Google Chrome e Microsoft Edge) che utilizzano il protocollo HTTP/2, HTTPS e CORS, il supporto lato client Blazor consente di utilizzare l'API Streams per permettere di caricare file di grandi dimensioni con lo streaming di richiesta .
Senza un browser basato su Chromium, un protocollo HTTP/2 o HTTPS, Blazor lato client legge i byte del file in un singolo buffer di matrice di JavaScript durante il trasferimento dei dati da JavaScript a C#, che è limitato a 2 GB o alla memoria disponibile del dispositivo. I caricamenti di file di grandi dimensioni potrebbero non riuscire per i caricamenti sul lato client usando il componente InputFile.
Lato client Blazor legge i byte del file in un singolo buffer di matrice JavaScript durante il marshalling dei dati da JavaScript a C#, che è limitato a 2 GB o alla memoria disponibile del dispositivo. I caricamenti di file di grandi dimensioni potrebbero non riuscire per i caricamenti sul lato client usando il componente InputFile. Si consiglia di adottare lo streaming delle richieste con ASP.NET Core 9.0 o versione successiva.
Considerazioni sulla sicurezza
Evitare IBrowserFile.Size
per i limiti delle dimensioni dei file
Evitare di usare IBrowserFile.Size per imporre un limite alle dimensioni del file. Anziché usare le dimensioni del file non sicure fornite dal client, specificare in modo esplicito le dimensioni massime del file. Nell'esempio seguente vengono usate le dimensioni massime del file assegnate a maxFileSize
:
- var fileContent = new StreamContent(file.OpenReadStream(file.Size));
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Sicurezza dei nomi file
Non usare mai un nome di file fornito dal client per salvare un file nell'archiviazione fisica. Creare un nome file sicuro per il file usando Path.GetRandomFileName() o Path.GetTempFileName() per creare un percorso completo (incluso il nome file) per l'archiviazione temporanea.
Razor HTML codifica automaticamente i valori delle proprietà per la visualizzazione. Il codice seguente è sicuro da usare:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
All'esterno di Razor, usare sempre HtmlEncode per codificare in modo sicuro i nomi di file da una richiesta di un utente.
Molte implementazioni devono includere un controllo dell'esistenza del file; in caso contrario, il file viene sovrascritto da un file con lo stesso nome. Fornire logica aggiuntiva per soddisfare le specifiche dell'app.
Esempi
Gli esempi seguenti illustrano il caricamento di più file in un componente. InputFileChangeEventArgs.GetMultipleFiles consente la lettura di più file. Specificare il numero massimo di file per impedire a un utente malintenzionato di caricare un numero maggiore di file rispetto all'app prevista. InputFileChangeEventArgs.File consente di leggere il primo file e solo se il caricamento del file non supporta più file.
InputFileChangeEventArgs si trova nel namespace Microsoft.AspNetCore.Components.Forms, che in genere è uno dei namespace nel file _Imports.razor
dell'app. Quando lo spazio dei nomi è presente nel _Imports.razor
file, fornisce ai membri dell'API l'accesso ai componenti dell'app.
I namespace nel file _Imports.razor
non vengono applicati ai file C# (.cs
). I file C# richiedono una direttiva esplicita using
all'inizio del file di classe:
using Microsoft.AspNetCore.Components.Forms;
Per testare i componenti di caricamento dei file, è possibile creare file di test di qualsiasi dimensione con PowerShell:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
Nel comando precedente:
- Il
{SIZE}
segnaposto è la dimensione del file in byte,2097152
ad esempio per un file da 2 MB. - Il
{PATH}
segnaposto è il percorso e il file con estensione di file, ad esempioD:/test_files/testfile2MB.txt
.
Esempio di caricamento di file sul lato server
Per usare il codice seguente, creare una Development/unsafe_uploads
cartella nella radice dell'app in esecuzione nell'ambiente Development
.
Poiché l'esempio usa l'ambiente dell'app come parte del percorso in cui vengono salvati i file, sono necessarie cartelle aggiuntive se vengono usati altri ambienti in fase di test e produzione. Ad esempio, creare una Staging/unsafe_uploads
cartella per l'ambiente Staging
. Creare una Production/unsafe_uploads
cartella per l'ambiente Production
.
Avviso
L'esempio salva i file senza analizzarne il contenuto e le indicazioni contenute in questo articolo non tengono conto di procedure consigliate di sicurezza aggiuntive per i file caricati. Nei sistemi di gestione temporanea e produzione disabilitare l'autorizzazione di esecuzione per la cartella di caricamento e analizzare i file con un'API scanner antivirus/antimalware immediatamente dopo il caricamento. Per altre informazioni, vedere Caricare file in ASP.NET Core.
FileUpload1.razor
:
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<PageTitle>File Upload 1</PageTitle>
<h1>File Upload Example 1</h1>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = [];
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;
}
}
Esempio di caricamento di file sul lato client
L'esempio seguente elabora i byte di file e non invia file a una destinazione esterna all'app. Per un esempio di componente Razor che invia un file a un server o a un servizio, vedere le sezioni seguenti:
Il componente presuppone che la modalità di rendering Interactive WebAssembly (InteractiveWebAssembly
) venga ereditata da un componente padre o applicata a livello globale all'app.
@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 restituisce i metadati esposti dal browser come proprietà. Usare questi metadati per la convalida preliminare.
Non considerare mai attendibili i valori delle proprietà precedenti, in particolare la Name proprietà per la visualizzazione nell'interfaccia utente. Considera tutti i dati forniti dall'utente come un rischio significativo per la sicurezza per l'app, il server e la rete. Per altre informazioni, vedere Caricare file in ASP.NET Core.
Caricare file su un server con rendering lato server
Questa sezione si applica ai componenti di Interactive Server in Blazor Web Apps o Blazor Server app.
L'esempio seguente illustra il caricamento di file da un'app lato server in un controller API Web back-end in un'app separata, possibilmente in un server separato.
Sul file Program
dell'app lato server, aggiungere IHttpClientFactory e i servizi correlati che consentono all'app di creare istanze HttpClient:
builder.Services.AddHttpClient();
Per altre informazioni, vedere Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET Core.
Per gli esempi in questa sezione:
- L'API Web viene eseguita nell'URL:
https://localhost:5001
- L'app lato server viene eseguita nell'URL:
https://localhost:5003
Per i test, gli URL precedenti vengono configurati nei Properties/launchSettings.json
file dei progetti.
La classe seguente UploadResult
mantiene il risultato di un file caricato. Quando un file non viene caricato nel server, viene restituito un codice di errore in ErrorCode
per la visualizzazione all'utente. Un nome file sicuro viene generato nel server per ogni file e restituito al client in StoredFileName
per la visualizzazione. I file vengono chiaveti tra il client e il server usando il nome file non sicuro/non attendibile in 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; }
}
Una procedura consigliata per la sicurezza per le app di produzione consiste nell'evitare l'invio di messaggi di errore ai client che potrebbero rivelare informazioni riservate su un'app, un server o una rete. Fornire messaggi di errore dettagliati può aiutare un utente malintenzionato a deviare attacchi su un'app, un server o una rete. Il codice di esempio in questa sezione restituisce solo un numero di codice di errore (int
) per la visualizzazione dal lato client del componente se si verifica un errore sul lato server. Se un utente richiede assistenza per il caricamento di file, fornisce il codice di errore per supportare il personale per la risoluzione dei ticket di supporto senza conoscere mai la causa esatta dell'errore.
La classe seguente LazyBrowserFileStream
definisce un tipo di flusso personalizzato che chiama OpenReadStream pigramente appena prima che vengano richiesti i primi byte del flusso. Il flusso non viene trasmesso dal browser al server fino all'inizio della lettura del flusso in .NET.
LazyBrowserFileStream.cs
:
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
: Stream
{
private readonly IBrowserFile file = file;
private readonly int maxAllowedSize = maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public override void Flush() => underlyingStream?.Flush();
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen() =>
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream? underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample;
internal sealed class LazyBrowserFileStream : Stream
{
private readonly IBrowserFile file;
private readonly int maxAllowedSize;
private Stream underlyingStream;
private bool isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => file.Size;
public override long Position
{
get => underlyingStream?.Position ?? 0;
set => throw new NotSupportedException();
}
public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
{
this.file = file;
this.maxAllowedSize = maxAllowedSize;
}
public override void Flush()
{
underlyingStream?.Flush();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
EnsureStreamIsOpen();
return underlyingStream.ReadAsync(buffer, cancellationToken);
}
[MemberNotNull(nameof(underlyingStream))]
private void EnsureStreamIsOpen()
{
underlyingStream ??= file.OpenReadStream(maxAllowedSize);
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
underlyingStream?.Dispose();
isDisposed = true;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
}
Componente seguente FileUpload2
:
- Consente agli utenti di caricare file dal client.
- Visualizza il nome file non attendibile/non sicuro fornito dal client nell'interfaccia utente. Il nome file non attendibile/non sicuro viene automaticamente codificato in HTML da Razor per un'adeguata visualizzazione nell'interfaccia utente.
Avviso
Non considerare attendibili i nomi di file forniti dai client per:
- Salvataggio del file in un file system o in un servizio.
- Visualizza nelle interfacce utente che non codificano automaticamente i nomi di file o tramite il codice per sviluppatori.
Per altre informazioni sulle considerazioni sulla sicurezza durante il caricamento di file in un server, vedere Caricare file in ASP.NET Core.
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Any())
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = [];
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; }
}
}
Se il componente limita il caricamento di file in un singolo file alla volta o se il componente adotta solo il rendering lato client (CSR, InteractiveWebAssembly
), il componente può evitare l'uso del LazyBrowserFileStream
e usare un Stream. Di seguito vengono illustrate le modifiche per il FileUpload2
componente:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Rimuovere la LazyBrowserFileStream
classe (LazyBrowserFileStream.cs
), perché non viene usata.
Se il componente limita i caricamenti di file in un singolo file alla volta, il componente può evitare l'uso di LazyBrowserFileStream
e usare .Stream Di seguito vengono illustrate le modifiche per il FileUpload2
componente:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Rimuovere la LazyBrowserFileStream
classe (LazyBrowserFileStream.cs
), perché non viene usata.
Il controller seguente nel progetto API Web salva i file caricati dal client.
Importante
Il controller in questa sezione è destinato all'uso in un progetto API Web separato dall'app Blazor . L'API Web deve attenuare gli attacchi XSRF/CSRF (Cross-Site Request Forgery) se gli utenti di caricamento di file sono autenticati.
Nota
L'associazione dei valori del modulo con l'attributo [FromForm]
non è supportata in modo nativo per le API minime in ASP.NET Core in .NET 6. Di conseguenza, l'esempio di controller seguente Filesave
non può essere convertito per l'uso di API minime. Il supporto per l'associazione da valori di modulo con API minime è disponibile in ASP.NET Core in .NET 7 o versione successiva.
Per usare il codice seguente, creare una Development/unsafe_uploads
cartella nella radice del progetto API Web per l'app in esecuzione nell'ambiente Development
.
Poiché l'esempio usa l'ambiente dell'app come parte del percorso in cui vengono salvati i file, sono necessarie cartelle aggiuntive se vengono usati altri ambienti in fase di test e produzione. Ad esempio, creare una Staging/unsafe_uploads
cartella per l'ambiente Staging
. Creare una Production/unsafe_uploads
cartella per l'ambiente Production
.
Avviso
L'esempio salva i file senza analizzarne il contenuto e le indicazioni contenute in questo articolo non tengono conto di procedure consigliate di sicurezza aggiuntive per i file caricati. Nei sistemi di gestione temporanea e produzione disabilitare l'autorizzazione di esecuzione per la cartella di caricamento e analizzare i file con un'API scanner antivirus/antimalware immediatamente dopo il caricamento. Per altre informazioni, vedere Caricare file in ASP.NET Core.
Controllers/FilesaveController.cs
:
using System.Net;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
Nel codice precedente viene GetRandomFileName chiamato per generare un nome di file sicuro. Non considerare mai attendibile il nome file fornito dal browser, poiché un cyberattacker può scegliere un nome di file esistente che sovrascrive un file esistente o invia un percorso che tenta di scrivere all'esterno dell'app.
L'app server deve registrare i servizi del controller e mappare gli endpoint del controller. Per ulteriori informazioni, consultare Il routaggio delle azioni del controller in ASP.NET Core.
Caricare i file su un server con il rendering lato client (CSR)
Questa sezione si applica ai componenti di cui è stato eseguito il rendering sul lato client (CSR) in Blazor Web Apps o Blazor WebAssembly app.
L'esempio seguente dimostra come caricare file su un controller di un'API Web back-end in un'app separata, possibilmente su un server distinto, partendo da un componente in un'app che adotta CSR o da un componente in un'app Blazor WebAssembly.
L'esempio adotta richiesta di streaming per un browser basato su Chromium (ad esempio, Google Chrome o Microsoft Edge) con protocollo HTTP/2 e HTTPS. Se non è possibile usare lo streaming delle richieste, Blazor si adatta senza problemi a API Fetch senza lo streaming delle richieste. Per altre informazioni, vedere la sezione Limiti di lettura e caricamento dei file.
La classe seguente UploadResult
mantiene il risultato di un file caricato. Quando un file non viene caricato nel server, viene restituito un codice di errore in ErrorCode
per la visualizzazione all'utente. Un nome file sicuro viene generato nel server per ogni file e restituito al client in StoredFileName
per la visualizzazione. I file vengono chiaveti tra il client e il server usando il nome file non sicuro/non attendibile in 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; }
}
Nota
La classe precedente UploadResult
può essere condivisa tra progetti client e basati su server. Quando i progetti client e server condividono la classe, aggiungere un'istruzione di importazione al file di _Imports.razor
di ogni progetto per il progetto condiviso. Ad esempio:
@using BlazorSample.Shared
Componente seguente FileUpload2
:
- Consente agli utenti di caricare file dal client.
- Visualizza il nome file non attendibile/non sicuro fornito dal client nell'interfaccia utente. Il nome file non attendibile/non sicuro viene codificato automaticamente in HTML da Razor per una visualizzazione sicura nell'interfaccia utente.
Una procedura consigliata per la sicurezza per le app di produzione consiste nell'evitare l'invio di messaggi di errore ai client che potrebbero rivelare informazioni riservate su un'app, un server o una rete. Fornire messaggi di errore dettagliati può aiutare un utente malintenzionato a deviare attacchi su un'app, un server o una rete. Il codice di esempio in questa sezione restituisce solo un numero di codice di errore (int
) per la visualizzazione dal lato client del componente se si verifica un errore sul lato server. Se un utente richiede assistenza per il caricamento di file, fornisce il codice di errore per supportare il personale per la risoluzione dei ticket di supporto senza conoscere mai la causa esatta dell'errore.
Avviso
Non considerare attendibili i nomi di file forniti dai client per:
- Salvataggio del file in un file system o in un servizio.
- Visualizza nelle interfacce utente che non codificano automaticamente i nomi di file o tramite il codice per sviluppatori.
Per altre informazioni sulle considerazioni sulla sicurezza durante il caricamento di file in un server, vedere Caricare file in ASP.NET Core.
Nel progetto server Blazor Web App aggiungere IHttpClientFactory e i servizi correlati nel file di Program
del progetto:
builder.Services.AddHttpClient();
I servizi di HttpClient devono essere aggiunti al progetto server perché il componente lato client è prerenderato nel server. Se disabiliti il prerendering per il seguente componente, non è necessario fornire i servizi HttpClient nel progetto server e non è necessario aggiungere la riga precedente al progetto server.
Per altre informazioni sull'aggiunta HttpClient di servizi a un'app ASP.NET Core, vedere Effettuare richieste HTTP con IHttpClientFactory in ASP.NET Core.
Il progetto client (.Client
) di un Blazor Web App deve anche registrare un HttpClient per le richieste HTTP POST a un controller API Web back-end. Confermare o aggiungere quanto segue al file del Program
progetto client:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
L'esempio precedente imposta l'indirizzo di base con builder.HostEnvironment.BaseAddress
(IWebAssemblyHostEnvironment.BaseAddress), che ottiene l'indirizzo di base per l'app ed è in genere derivato dal <base>
valore del href
tag nella pagina host. Se stai chiamando un'API web esterna, imposta l'URI all'indirizzo base dell'API web.
Un'app Blazor WebAssembly autonoma che carica i file in un'API Web server separata usa un denominato HttpClient
o imposta la registrazione predefinita del servizio HttpClient in modo che punti all'endpoint dell'API Web. Nell'esempio seguente in cui l'API Web è ospitata localmente sulla porta 5001, l'indirizzo di base è https://localhost:5001
:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri("https://localhost:5001") });
In un Blazor Web Appaggiungere lo spazio dei nomi Microsoft.AspNetCore.Components.WebAssembly.Http alle direttive del componente:
@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; }
}
}
Il controller seguente nel progetto lato server salva i file caricati dal client.
Nota
L'associazione dei valori del modulo con l'attributo [FromForm]
non è supportata in modo nativo per le API minime in ASP.NET Core in .NET 6. Di conseguenza, l'esempio di controller seguente Filesave
non può essere convertito per l'uso di API minime. Il supporto per l'associazione da valori di modulo con API minime è disponibile in ASP.NET Core in .NET 7 o versione successiva.
Per usare il codice seguente, creare una Development/unsafe_uploads
cartella nella radice del progetto lato server per l'app in esecuzione nell'ambiente Development
.
Poiché l'esempio usa l'ambiente dell'app come parte del percorso in cui vengono salvati i file, sono necessarie cartelle aggiuntive se vengono usati altri ambienti in fase di test e produzione. Ad esempio, creare una Staging/unsafe_uploads
cartella per l'ambiente Staging
. Creare una Production/unsafe_uploads
cartella per l'ambiente Production
.
Avviso
L'esempio salva i file senza analizzarne il contenuto e le indicazioni contenute in questo articolo non tengono conto di procedure consigliate di sicurezza aggiuntive per i file caricati. Nei sistemi di gestione temporanea e produzione disabilitare l'autorizzazione di esecuzione per la cartella di caricamento e analizzare i file con un'API scanner antivirus/antimalware immediatamente dopo il caricamento. Per altre informazioni, vedere Caricare file in ASP.NET Core.
Nell'esempio seguente per un'app Blazor WebAssembly ospitata o in cui viene usato un progetto condiviso per fornire la classe UploadResult
aggiungere lo spazio dei nomi del progetto condiviso:
using BlazorSample.Shared;
Consigliamo di utilizzare un namespace per il seguente controller, ad esempio 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);
}
}
Nel codice precedente viene GetRandomFileName chiamato per generare un nome di file sicuro. Non considerare mai attendibile il nome file fornito dal browser, poiché un cyberattacker può scegliere un nome di file esistente che sovrascrive un file esistente o invia un percorso che tenta di scrivere all'esterno dell'app.
L'app server deve registrare i servizi dei controller e mappare gli endpoint del controller. Per ulteriori informazioni, consultare Instradamento delle azioni del controller in ASP.NET Core. È consigliabile aggiungere servizi controller con AddControllersWithViews per attenuare automaticamente gli attacchi XSRF/CSRF (Cross-Site Request Forgery) per gli utenti autenticati. Se si usa semplicemente AddControllers, l'antiforgeria non viene abilitata automaticamente. Per altre informazioni, vedere Instradamento delle azioni del controller in ASP.NET Core.
La configurazione CORS (Cross-Origin Requests) nel server è necessaria per flusso di richieste quando il server è ospitato in un'origine diversa e viene sempre effettuata una richiesta preliminare dal client. Nella configurazione del servizio del file Program
del server (il progetto server di un Blazor Web App o l'API Web del server back-end di un'app Blazor WebAssembly), il criterio CORS predefinito seguente è adatto per i test con gli esempi in questo articolo. Il client effettua la richiesta locale dalla porta 5003. Modificare il numero di porta in modo che corrisponda alla porta dell'app client in uso:
Configurare le richieste cross-origin (CORS) sul server. Nella configurazione del servizio del file Program
del server (il progetto server di un Blazor Web App o l'API Web del server back-end di un'app Blazor WebAssembly), il criterio CORS predefinito seguente è adatto per i test con gli esempi in questo articolo. Il client effettua la richiesta locale dalla porta 5003. Modificare il numero di porta in modo che corrisponda alla porta dell'app client in uso:
Configurare le richieste tra le origini (CORS) nel server. Nella configurazione del servizio del file di Program
dell'API Web del server back-end, il criterio CORS predefinito seguente è adatto per il test con gli esempi in questo articolo. Il client effettua la richiesta locale dalla porta 5003. Modificare il numero di porta in modo che corrisponda alla porta dell'app client in uso:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
Dopo aver chiamato UseHttpsRedirection nel file Program
, chiamare UseCors per aggiungere il middleware CORS:
app.UseCors();
Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core.
Configurare la dimensione massima del corpo della richiesta del server e il limite di lunghezza del corpo multipart se i limiti limitano la dimensione di caricamento.
Per il server Kestrel, impostare MaxRequestBodySize (impostazione predefinita: 30.000.000 byte) e FormOptions.MultipartBodyLengthLimit (impostazione predefinita: 134.217.728 byte). Impostare la variabile maxFileSize
nel componente e il controller sullo stesso valore.
Nella seguente configurazione del file Program
Kestrel (il progetto server di un Blazor Web App o l'API web del server back-end di un'app Blazor WebAssembly), il segnaposto {LIMIT}
è il limite in byte:
using Microsoft.AspNetCore.Http.Features;
...
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = {LIMIT};
});
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = {LIMIT};
});
Annullare il caricamento di un file
Un componente di caricamento di file può rilevare quando un utente ha annullato un caricamento usando un oggetto CancellationToken quando si chiama IBrowserFile.OpenReadStream o StreamReader.ReadAsync.
Creare un CancellationTokenSource per il componente InputFile
. All'inizio del OnInputFileChange
metodo verificare se è in corso un caricamento precedente.
Se è in corso un caricamento di file:
- Richiamare Cancel per il caricamento precedente.
- Creare un nuovo oggetto CancellationTokenSource per il caricamento successivo e passare il CancellationTokenSource.Token a OpenReadStream o ReadAsync.
Caricare file dal lato server mostrando lo stato di avanzamento
L'esempio seguente illustra come caricare i file in un'app lato server con lo stato di avanzamento del caricamento visualizzato all'utente.
Per usare l'esempio seguente in un'app di test:
-
Creare una cartella per salvare i file caricati per l'ambiente
Development
:Development/unsafe_uploads
. - Configurare le dimensioni massime del file (
maxFileSize
, 15 KB nell'esempio seguente) e il numero massimo di file consentiti (maxAllowedFiles
, 3 nell'esempio seguente). - Impostare il buffer su un valore diverso (10 KB nell'esempio seguente), se necessario, per aumentare la granularità nella creazione di report in corso. Non è consigliabile usare un buffer di dimensioni superiori a 30 KB a causa di problemi di prestazioni e sicurezza.
Avviso
L'esempio salva i file senza analizzarne il contenuto e le indicazioni contenute in questo articolo non tengono conto di procedure consigliate di sicurezza aggiuntive per i file caricati. Nei sistemi di gestione temporanea e produzione disabilitare l'autorizzazione di esecuzione per la cartella di caricamento e analizzare i file con un'API scanner antivirus/antimalware immediatamente dopo il caricamento. Per altre informazioni, vedere Caricare file in ASP.NET Core.
FileUpload3.razor
:
@page "/file-upload-3"
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<PageTitle>File Upload 3</PageTitle>
<h1>File Upload Example 3</h1>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = [];
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;
}
}
Per altre informazioni, vedere le risorse API seguenti:
- FileStream: fornisce un oggetto Stream per un file, che supporta le operazioni di lettura e scrittura asincrone e sincrone.
-
FileStream.ReadAsync: il componente precedente
FileUpload3
legge il flusso in modo asincrono con ReadAsync. La lettura di un flusso in modo sincrono con Read non è supportata nei Razor componenti.
Flussi di file
Con l'interattività del server, i dati dei file vengono trasmessi tramite la SignalR connessione nel codice .NET sul server mentre il file viene letto.
RemoteBrowserFileStreamOptions consente di configurare le caratteristiche di caricamento dei file.
Per un componente sottoposto a rendering WebAssembly, i dati dei file vengono trasmessi direttamente nel codice .NET all'interno del browser.
Caricare l'anteprima dell'immagine
Per un'anteprima delle immagini durante il caricamento, inizia aggiungendo un InputFile
con un riferimento al componente e un OnChange
come gestore:
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Aggiungere un elemento immagine con un element reference che funge da segnaposto per l'anteprima dell'immagine.
<img @ref="previewImageElem" />
Aggiungere i riferimenti associati:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}
In JavaScript, aggiungere una funzione chiamata con un elemento HTML input
e img
che esegue le operazioni seguenti:
- Estrae il file selezionato.
- Crea un URL di oggetto con
createObjectURL
. - Imposta un listener di eventi per revocare l'URL dell'oggetto con
revokeObjectURL
dopo il caricamento dell'immagine, così non si verificano perdite di memoria. - Imposta l'origine
img
dell'elemento per visualizzare l'immagine.
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
}
Infine, usa un elemento inserito IJSRuntime per aggiungere il OnChange
handler che chiama la funzione JavaScript.
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
L'esempio precedente riguarda il caricamento di una singola immagine. L'approccio può essere espanso per supportare le immagini multiple
.
Il componente seguente FileUpload4
illustra l'esempio completo.
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);
}
Salvare file di piccole dimensioni direttamente in un database con EF Core
Molte app ASP.NET Core usano Entity Framework Core (EF Core) per gestire le operazioni del database. Il salvataggio di anteprime e avatar direttamente nel database è un requisito comune. Questa sezione illustra un approccio generale che può essere ulteriormente migliorato per le app di produzione.
Il modello seguente:
- Si basa sull'app del tutorial del database di film Blazor.
- Può essere migliorato con codice aggiuntivo per le dimensioni del file e il tipo di contenuto feedback di convalida.
- Comporta una riduzione delle prestazioni e il rischio di DoS. Valutare attentamente il rischio durante la lettura di qualsiasi file in memoria e prendere in considerazione approcci alternativi, soprattutto per i file di dimensioni maggiori. Gli approcci alternativi includono il salvataggio dei file direttamente su disco o un servizio di terze parti per i controlli antivirus/antimalware, l'ulteriore elaborazione e la gestione ai client.
Per il funzionamento dell'esempio seguente in un Blazor Web App (ASP.NET Core 8.0 o versione successiva), il componente deve adottare una modalità di rendering interattivo (ad esempio, @rendermode InteractiveServer
) per chiamare HandleSelectedThumbnail
in caso di modifica del file componente InputFile
(OnChange
parametro/evento).
Blazor Server i componenti dell'app sono sempre interattivi e non richiedono una modalità di rendering.
Nell'esempio seguente, una piccola anteprima (<= 100 KB) viene salvata in un IBrowserFile e archiviata in un database con EF Core. Se un file non è selezionato dall'utente per il componente InputFile
, nel database viene salvata un'anteprima predefinita.
La miniatura predefinita (default-thumbnail.jpg
) si trova nella cartella principale del progetto e ha l'impostazione Copia nella directory di output di Copia se più recente:
Il modello di Movie
(Movie.cs
) ha una proprietà (Thumbnail
) per contenere i dati dell'immagine di anteprima:
[Column(TypeName = "varbinary(MAX)")]
public byte[]? Thumbnail { get; set; }
I dati dell'immagine vengono archiviati come byte nel database come varbinary(MAX)
. L'app base 64 codifica i byte per la visualizzazione perché i dati con codifica base 64 sono circa un terzo maggiore dei byte non elaborati dell'immagine, pertanto i dati immagine di base 64 richiedono un'archiviazione aggiuntiva del database e riducono le prestazioni delle operazioni di lettura/scrittura del database.
I componenti che visualizzano la miniatura passano i dati dell'immagine all'attributo src
del tag img
come dati JPEG codificati in base 64.
<img src="data:image/jpeg;base64,@Convert.ToBase64String(movie.Thumbnail)"
alt="User thumbnail" />
Nel componente Create
seguente viene elaborato un caricamento di immagini. È possibile migliorare ulteriormente l'esempio con la convalida personalizzata per il tipo di file e le dimensioni usando gli approcci in ASP.NET Core Blazor convalida dei moduli. Per visualizzare il componente Create
completo senza il codice di caricamento dell'anteprima nell'esempio seguente, vedere l'app di esempio BlazorWebAppMovies
nel repository GitHub degli esempi Blazor.
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");
}
}
Lo stesso approccio verrà adottato nel componente Edit
con una modalità di rendering interattiva se gli utenti sono autorizzati a modificare l'immagine di anteprima di un film.
Caricare file in un servizio esterno
Invece di un'app che gestisce i byte di caricamento dei file e del server dell'app che riceve i file caricati, i client possono caricare direttamente i file in un servizio esterno. L'app può elaborare in modo sicuro i file dal servizio esterno su richiesta. Questo approccio rafforza l'app e il relativo server contro attacchi dannosi e potenziali problemi di prestazioni.
Si consideri un approccio che utilizza File di Azure, Archiviazione BLOB di Azure o un servizio di terze parti con i seguenti potenziali vantaggi:
- Caricare file dal client direttamente in un servizio esterno con una libreria client JavaScript o REST API. Ad esempio, Azure offre le API e le librerie client seguenti:
- Autorizzare i caricamenti degli utenti con un token di firma di accesso condiviso delegato dall'utente generato dall'app (lato server) per ogni caricamento di file client. Ad esempio, Azure offre le seguenti funzionalità SAS:
- Fornire ridondanza automatica e backup dei file condivisi.
- Limitare i caricamenti con quote. Si noti che le quote di Archiviazione BLOB di Azure vengono impostate a livello di account, non a livello di contenitore. Tuttavia, le quote di Azure Files sono a livello di condivisione dei file e potrebbero offrire un miglior controllo sui limiti di caricamento. Per altre informazioni, vedere i documenti di Azure collegati in precedenza in questo elenco.
- Proteggere i file con crittografia lato server (SSE).
Per ulteriori informazioni su Archiviazione Blob di Azure e Azure Files, consulta la documentazione di archiviazione di Azure.
Limite delle dimensioni dei messaggi del server SignalR
I caricamenti di file potrebbero avere esito negativo anche prima dell'avvio, quando Blazor recupera i dati relativi ai file che superano le dimensioni massime SignalR del messaggio.
SignalR definisce un limite di dimensioni del messaggio che si applica a ogni messaggio Blazor ricevuto e i InputFile file del componente vengono inviati al server nei messaggi che rispettano il limite configurato. Tuttavia, il primo messaggio, che indica il set di file da caricare, viene inviato come singolo messaggio univoco. Le dimensioni del primo messaggio possono superare il limite di dimensioni del SignalR messaggio. Il problema non è correlato alle dimensioni dei file, è correlato al numero di file.
L'errore registrato è simile al seguente:
Errore: connessione disconnessa con errore 'Errore: il server ha restituito un errore alla chiusura: Connessione chiusa con un errore'. e.log @ blazor.server.js:1
Quando si caricano file, il raggiungimento del limite di dimensioni del messaggio per il primo messaggio è raro. Se viene raggiunto il limite, l'app può configurare HubOptions.MaximumReceiveMessageSize con un valore maggiore.
Per ulteriori informazioni sulla configurazione di SignalR e su come impostare MaximumReceiveMessageSize, vedere la guida ASP.NET Core BlazorSignalR.
Numero massimo di chiamate parallele per impostazione dell'hub client
Blazor si basa su MaximumParallelInvocationsPerClient impostato a 1, che è il valore predefinito.
Aumentare il valore comporta un'elevata probabilità che le operazioni CopyTo
generino System.InvalidOperationException: 'Reading is not allowed after reader was completed.'
. Per altre informazioni, vedere MaximumParallelInvocationsPerClient > 1 interrompe il caricamento dei file in Blazor Server modalità (dotnet/aspnetcore
#53951).
Risoluzione dei problemi
La riga che chiama IBrowserFile.OpenReadStream genera un'eccezione System.TimeoutException:
System.TimeoutException: Did not receive any data in the allotted time.
Possibili cause:
Usando il contenitore di Inversione di Controllo Autofac () al posto del contenitore di iniezione delle dipendenze incorporato di ASP.NET Core () nelle versioni di ASP.NET Core precedenti alla 9.0. Per risolvere il problema, impostare DisableImplicitFromServicesParameters su
true
nelle opzioni dell'hub del gestore del circuito lato server. Per altre informazioni, vedere FileUpload: Non sono stati ricevuti dati nel tempo assegnato (dotnet/aspnetcore
#38842).Non portare a termine la lettura del flusso. Questo non è un problema del framework. Intercettare l'eccezione e esaminarla ulteriormente nell'ambiente o nella rete locale.
- Uso del rendering lato server e della funzione OpenReadStream su più file prima di leggerli completamente. Per risolvere il problema, usare la classe
LazyBrowserFileStream
e l'approccio descritti nella sezione Caricare file in un server con rendering lato server di questo articolo.