ASP.NET caricamenti di file di base Blazor
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 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 8 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 esegue il InputFile rendering di un elemento HTML <input>
di tipo file
per i caricamenti 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 sottoposto a rendering:
<input multiple="" type="file" _bl_2="">
Nota
Nell'esempio precedente, l'attributo dell'elemento _bl_2
viene usato per Blazorl'elaborazione <input>
interna dell'elemento.
Per leggere i dati da un file selezionato dall'utente, chiamare IBrowserFile.OpenReadStream il file e leggere dal flusso restituito. Per altre informazioni, vedere la sezione Flussi di file .
OpenReadStream applica una dimensione massima in byte del relativo Streamoggetto . 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.
Se è necessario accedere a un Stream oggetto che rappresenta i byte del file, usare IBrowserFile.OpenReadStream. Evitare di leggere il flusso di file in ingresso direttamente in memoria contemporaneamente. Ad esempio, non copiare tutti i byte del file in un MemoryStream oggetto 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 rappresenta browserFile
il file caricato e implementa IBrowserFile. Le implementazioni di lavoro per IBrowserFile sono illustrate nei componenti di caricamento dei file più avanti in questo articolo.
Supportato: l'approccio Stream seguente è consigliato perché il file viene fornito direttamente al consumer, un oggetto FileStream che crea il file nel percorso specificato:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);
Microsoft Archiviazione BLOB di Azure perché il file Stream viene fornito direttamente a UploadBlobAsync:
l'approccio seguente è consigliato perawait blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
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()).ReadToEndAsync();
Non consigliato: l'approccio seguente non è consigliato per Microsoft Archiviazione BLOB di Azure perché il contenuto del file viene copiato in una MemoryStream in memoria (memoryStream
) prima di Stream chiamare UploadBlobAsync:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().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 Blazor WebAssembly le app.
Utenti del contenitore Autofac Inversion of Control (IoC)
Se si usa il contenitore Autofac Inversion of Control (IoC) anziché il contenitore predefinito di inserimento delle dipendenze di ASP.NET Core, impostare su DisableImplicitFromServicesParameters true
nelle opzioni dell'hub del gestore del circuito sul lato server. Per altre informazioni, vedere FileUpload: Non sono stati ricevuti dati nel tempo assegnato (dotnet/aspnetcore
#38842).
Limiti di lettura e caricamento dei file
Lato server o lato client, non esiste un limite di dimensioni di lettura o caricamento di file specifico per il InputFile componente. Tuttavia, il lato Blazor client 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 (> 250 MB) potrebbero non riuscire per i caricamenti sul lato client usando il InputFile componente. Per altre informazioni, vedere le discussioni seguenti:
La dimensione massima supportata del file per il InputFile componente è di 2 GB. Inoltre, il lato Blazor client 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 (> 250 MB) potrebbero non riuscire per i caricamenti sul lato client usando il InputFile componente. Per altre informazioni, vedere le discussioni seguenti:
- Il Blazor componente InputFile deve gestire la suddivisione in blocchi quando il file viene caricato (dotnet/runtime #84685)
- Richiedere il caricamento dello streaming tramite il gestore http (dotnet/runtime #36634)
Per i caricamenti di file lato client di grandi dimensioni che hanno esito negativo quando si tenta di usare il InputFile componente, è consigliabile creare una suddivisione in blocchi di file di grandi dimensioni con un componente personalizzato usando più richieste di intervallo HTTP anziché usare il InputFile componente .
Il lavoro è attualmente pianificato per .NET 9 (fine 2024) per risolvere la limitazione del caricamento delle dimensioni dei file sul lato client.
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 nello Microsoft.AspNetCore.Components.Forms spazio dei nomi, che in genere è uno degli spazi dei nomi nel file dell'app _Imports.razor
. Quando lo spazio dei nomi è presente nel _Imports.razor
file, fornisce ai membri dell'API l'accesso ai componenti dell'app.
Gli spazi dei nomi nel _Imports.razor
file 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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileName);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
loadedFiles.Add(file);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private void LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
IBrowserFile 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 in 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.
Nel file dell'app Program
sul lato server aggiungere IHttpClientFactory e servizi correlati che consentono all'app di creare HttpClient istanze:
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 file dei Properties/launchSettings.json
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 in modo differire 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 codificato automaticamente in HTML per Razor la visualizzazione sicura 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 = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger
<h1>File Upload Example 2</h1>
<p>
This example requires a backend server API to function. For more information,
see the <em>Upload files to a server</em> section
of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
var response =
await client.PostAsync("https://localhost:5001/Filesave",
content);
if (response.IsSuccessStatusCode)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
using var responseStream =
await response.Content.ReadAsStreamAsync();
var newUploadResults = await JsonSerializer
.DeserializeAsync<IList<UploadResult>>(responseStream, options);
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string Name { get; set; }
}
}
Se il componente limita i caricamenti di file in un singolo file alla volta o se il componente adotta solo il rendering lato client interattivo (CSR, InteractiveWebAssembly
), 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.
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 controller e gli endpoint del controller di mapping. Per altre informazioni, vedere Routing alle azioni del controller in ASP.NET Core.
Caricare file in 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 illustra il caricamento di file in un controller API Web back-end in un'app separata, possibilmente in un server separato, da un componente in un Blazor Web App che adotta csr o un componente in un'app Blazor WebAssembly .
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'importazione al file di _Imports.razor
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 per Razor la 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.
Blazor Web App Nel progetto principale aggiungere IHttpClientFactory e i servizi correlati nel file del Program
progetto:
builder.Services.AddHttpClient();
I HttpClient
servizi devono essere aggiunti al progetto principale perché il componente lato client è prerenderato nel server. Se si disabilita la prerendering per il componente seguente, non è necessario fornire i HttpClient
servizi nell'app principale e non è necessario aggiungere la riga precedente al progetto principale.
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 per HttpClient 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 si chiama un'API Web esterna, impostare l'URI sull'indirizzo di base dell'API Web.
Specificare l'attributo modalità di rendering Interactive WebAssembly nella parte superiore del componente seguente in un Blazor Web Appoggetto :
@rendermode InteractiveWebAssembly
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName);
if (result is null)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result = new();
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string Name { get; set; }
}
}
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 aggiornare lo spazio dei nomi del progetto condiviso in modo che corrisponda al progetto condiviso se un progetto condiviso fornisce la UploadResult
classe .
Controllers/FilesaveController.cs
:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
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 controller e gli endpoint del controller di mapping. Per altre informazioni, vedere Routing alle azioni del controller in ASP.NET Core.
Annullare il caricamento di un file
Un componente di caricamento di file può rilevare quando un utente ha annullato un caricamento usando un CancellationToken oggetto quando si chiama in IBrowserFile.OpenReadStream o StreamReader.ReadAsync.
Creare un CancellationTokenSource oggetto per il InputFile
componente. All'inizio del OnInputFileChange
metodo verificare se è in corso un caricamento precedente.
Se è in corso un caricamento di file:
- Chiamare Cancel il caricamento precedente.
- Creare un nuovo CancellationTokenSource oggetto per il caricamento successivo e passare a CancellationTokenSource.Token OpenReadStream o ReadAsync.
Caricare file sul lato server con 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 = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
Logger.LogInformation(
"Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
file.Name, trustedFileName);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
@page "/file-upload-3"
@using System
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment
<h3>Upload Files</h3>
<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>
<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>
<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}
@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads", trustedFileName);
await using FileStream writeStream = new(path, FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];
while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
{
totalRead += bytesRead;
await writeStream.WriteAsync(buffer, 0, bytesRead);
progressPercent = Decimal.Divide(totalRead, file.Size);
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
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 nel server durante la lettura del file.
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 dell'immagine del caricamento di immagini, iniziare aggiungendo un InputFile
componente con un riferimento al componente e un OnChange
gestore:
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Aggiungere un elemento image con un riferimento all'elemento , 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 denominata con un html input
e img
un elemento 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, in modo che la memoria non venga persa. - 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, usare un elemento inserito IJSRuntime per aggiungere il OnChange
gestore 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 multiple
le immagini.
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);
}
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 usa File di Azure, Archiviazione BLOB di Azure o un servizio di terze parti con i potenziali vantaggi seguenti:
- Caricare file dal client direttamente in un servizio esterno con una libreria client o REST un'API JavaScript. 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. Azure offre ad esempio le funzionalità di firma di accesso condiviso seguenti:
- Fornire ridondanza automatica e backup della condivisione file.
- 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, File di Azure quote sono a livello di condivisione file e potrebbero fornire un migliore 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 altre informazioni su Archiviazione BLOB di Azure e File di Azure, vedere la documentazione di Archiviazione di Azure.
Limite delle dimensioni dei messaggi sul lato SignalR server
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 altre informazioni sulla configurazione e su SignalR come impostare MaximumReceiveMessageSize, vedere linee guida ASP.NET CoreBlazorSignalR.
Numero massimo di chiamate parallele per impostazione dell'hub client
Blazor si basa su impostato su MaximumParallelInvocationsPerClient 1, ovvero il valore predefinito.
L'aumento del valore comporta un'elevata probabilità che CopyTo
le operazioni 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:
Uso del contenitore Autofac Inversion of Control (IoC) invece del contenitore predefinito ASP.NET contenitore di inserimento delle dipendenze Core. Per risolvere il problema, impostare su DisableImplicitFromServicesParameters
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 leggendo il flusso fino al completamento. Questo non è un problema del framework. Intercettare l'eccezione e esaminarla ulteriormente nell'ambiente o nella rete locale.
- Uso del rendering lato server e chiamata OpenReadStream su più file prima di leggerli fino al completamento. Per risolvere il problema, usare la classe e l'approccio
LazyBrowserFileStream
descritti nella sezione Caricare file in un server con rendering lato server di questo articolo.