Uploads de arquivo Blazor do ASP.NET Core
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Este artigo explica como carregar arquivos no Blazor com o componente InputFile.
Carregamentos de arquivos
Aviso
Sempre siga as práticas recomendadas de segurança ao permitir que os usuários carreguem arquivos. Saiba mais em Carregar arquivos no ASP.NET Core.
Use o componente InputFile para ler os dados do arquivo do navegador no código do .NET. O componente InputFile renderiza um elemento <input>
HTML do tipo file
para uploads de arquivo único. Adicione o atributo multiple
para permitir que o usuário carregue vários arquivos ao mesmo tempo.
A seleção de arquivo não é cumulativa no uso de um componente InputFile ou sua HTML <input type="file">
subjacente, ou seja, você não pode adicionar arquivos a uma seleção de arquivo existente. O componente sempre substitui a seleção inicial do arquivo do usuário, ou seja, as referências de arquivo de seleções anteriores não estão disponíveis.
O componente InputFile a seguir executa o método LoadFiles
quando o evento OnChange (change
) ocorre. Um InputFileChangeEventArgs fornece acesso à lista de arquivos selecionada e detalhes sobre cada arquivo:
<InputFile OnChange="LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
HTML renderizado:
<input multiple="" type="file" _bl_2="">
Observação
No exemplo anterior, o atributo _bl_2
do elemento <input>
é usado para o processamento interno de Blazor.
Para ler dados de um arquivo selecionado pelo usuário, chame IBrowserFile.OpenReadStream no arquivo e leia do fluxo retornado. Para obter mais informações, confira a seção Fluxos de arquivos.
OpenReadStream impõe um tamanho máximo em bytes de seu Stream. A leitura de um ou vários arquivos com mais de 500 KB resulta em uma exceção. Esse limite impede que os desenvolvedores leiam acidentalmente arquivos grandes na memória. O parâmetro maxAllowedSize
de OpenReadStream pode ser usado para especificar um tamanho maior, se necessário.
Se você precisar de acesso a um Stream que represente os bytes do arquivo, use IBrowserFile.OpenReadStream. Evite ler o fluxo de arquivos de entrada diretamente na memória de uma só vez. Por exemplo, não copie todos os bytes do arquivo em um MemoryStream nem leia todo o fluxo em uma matriz de bytes de uma só vez. Essas abordagens podem resultar em desempenho degradado do aplicativo e risco potencial de DoS (negação de serviço), especialmente para componentes do lado do servidor. Em vez disso, considere adotar uma das seguintes abordagens:
- Copie o fluxo diretamente para um arquivo em disco sem lê-lo na memória. Observe que os aplicativos Blazor que executam código no servidor não podem acessar diretamente o sistema de arquivos do cliente.
- Carregue arquivos do cliente diretamente para um serviço externo. Para obter mais informações, confira a seção Carregar arquivos em um serviço externo.
Nos exemplos a seguir, browserFile
representa o arquivo carregado e implementa IBrowserFile. As implementações de trabalho para IBrowserFile são mostradas nos componentes de upload de arquivo mais adiante neste artigo.
✔️Com suporte: a abordagem abaixo é recomendada porque o Stream do arquivo é fornecido diretamente ao consumidor, um FileStream que cria o arquivo no caminho fornecido:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);
✔️Com suporte: a abordagem abaixo é recomendada para o Armazenamento de Blobs do Microsoft Azure porque o Stream do arquivo é fornecido diretamente a UploadBlobAsync:
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());
Não recomendado: a abordagem abaixo NÃO é recomendada porque o conteúdo Stream do arquivo é lido em um String na memória (reader
):
var reader =
await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();
Não recomendado: a abordagem abaixo NÃO é recomendada para o Armazenamento de Blobs do Microsoft Azure porque o conteúdo Stream do arquivo é copiado em um MemoryStream na memória (memoryStream
) antes de chamar UploadBlobAsync:
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
Um componente que recebe um arquivo de imagem pode chamar o método de conveniência BrowserFileExtensions.RequestImageFileAsync no arquivo a fim de redimensionar os dados da imagem no runtime JavaScript do navegador antes que a imagem seja transmitida ao aplicativo. Casos de uso de chamada de RequestImageFileAsync são mais apropriados para aplicativos Blazor WebAssembly.
Usuários de contêiner de IoC (Inversão de Controle) do Autofac
Se você estiver usando o contêiner de IoC (Inversão de Controle) do Autofac em vez do contêiner de injeção de dependência do ASP.NET Core interno, defina DisableImplicitFromServicesParameters como true
nas opções do hub do manipulador de circuito do lado do servidor. Para obter mais informações, consulte FileUpload: não recebeu nenhum dado no tempo alocado (dotnet/aspnetcore
#38842).
Limites de tamanho do arquivo para upload e leitura
Do lado do servidor ou do cliente, não há nenhum limite de tamanho de carregamento ou leitura de arquivo especificamente para o componente InputFile. No entanto, o Blazor do lado do cliente lê os bytes do arquivo em um único buffer de matriz JavaScript ao realizar marshaling dos dados de JavaScript para C#, que é limitado a 2 GB ou à memória disponível do dispositivo. Carregamentos de arquivos grandes (> 250 MB) podem falhar para carregamentos do lado do cliente usando o componente InputFile. Para saber mais, consulte as seguintes discussões:
O tamanho máximo de arquivo com suporte para o componente InputFile é de 2 GB. Além disso, o Blazor do lado do cliente lê os bytes do arquivo em único buffer de matriz JavaScript ao realizar marshaling dos dados de JavaScript para C#, que é limitado a 2 GB ou à memoria disponível do dispositivo. Carregamentos de arquivos grandes (> 250 MB) podem falhar para carregamentos do lado do cliente usando o componente InputFile. Para saber mais, consulte as seguintes discussões:
- O componente InputFile Blazor deve lidar com a separação em partes quando o arquivo é carregado (dotnet/runtime #84685)
- Carregamento de Streaming de Solicitação por meio do manipulador http (dotnet/runtime #36634)
Para carregamentos de arquivos grandes do lado do cliente que falham ao tentar usar o componente InputFile, recomendamos separar os arquivos grandes em partes com um componente personalizado usando várias solicitações de intervalo HTTP em vez de usar o componente InputFile.
Atualmente, o trabalho está agendado para o .NET 9 (final de 2024) para resolver a limitação de carregamento do tamanho do arquivo do lado do cliente.
Exemplos
Os exemplos a seguir demonstram vários carregamentos de arquivos em um componente. InputFileChangeEventArgs.GetMultipleFiles permite a leitura de vários arquivos. Especifique o número máximo de arquivos para impedir que um usuário mal-intencionado carregue um número maior de arquivos do que o aplicativo espera. InputFileChangeEventArgs.File permitirá a leitura do primeiro e único arquivo se o upload de arquivo não der suporte a vários arquivos.
InputFileChangeEventArgs está no namespace Microsoft.AspNetCore.Components.Forms, que normalmente é um dos namespaces no arquivo _Imports.razor
do aplicativo. Quando o namespace está presente no arquivo _Imports.razor
, ele fornece acesso de membro da API aos componentes do aplicativo.
Namespaces no arquivo _Imports.razor
não são aplicados a arquivos C# (.cs
). Os arquivos C# exigem uma diretiva using
explícita na parte superior do arquivo de classe:
using Microsoft.AspNetCore.Components.Forms;
Para testar componentes de upload de arquivo, você poderá criar arquivos de teste de qualquer tamanho com o PowerShell:
$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)
No comando anterior:
- O espaço reservado
{SIZE}
é o tamanho do arquivo em bytes (por exemplo,2097152
para um arquivo de 2 MB). - O espaço reservado
{PATH}
é o caminho e o arquivo com extensão de arquivo (por exemplo,D:/test_files/testfile2MB.txt
).
Exemplo de carregamento de arquivo do lado do servidor
Para usar o código a seguir, crie uma pasta Development/unsafe_uploads
na raiz do aplicativo em execução no ambiente de Development
.
Como o exemplo usa o ambiente do aplicativo como parte do caminho em que os arquivos são salvos, pastas adicionais serão exigidas se outros ambientes forem usados em testes e produção. Por exemplo, crie uma pasta Staging/unsafe_uploads
para o ambiente Staging
. Crie uma pasta Production/unsafe_uploads
para o ambiente Production
.
Aviso
O exemplo salva arquivos sem verificar seu conteúdo, e as diretrizes neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparo e produção, desabilite a permissão de execução na pasta de upload e examine arquivos com uma API verificadora de antivírus/antimalware imediatamente após o upload. Saiba mais em Carregar arquivos no 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;
}
}
Exemplo de carregamento de arquivo do lado do cliente
O exemplo a seguir processa bytes de arquivo e não envia arquivos para um destino fora do aplicativo. Para obter um exemplo de um componente Razor que envia um arquivo para um servidor ou serviço, confira as seguintes seções:
- Carregar arquivos em um servidor com CSR (renderização do lado do cliente)
- Carregar arquivos em um serviço externo
O componente pressupõe que o modo de renderização do WebAssembly Interativo (InteractiveWebAssembly
) é herdado de um componente pai ou aplicado globalmente ao aplicativo.
@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 retorna metadados expostos pelo navegador como propriedades. Use esses metadados na validação preliminar.
Nunca confie nos valores das propriedades anteriores, especialmente a propriedade Name para exibição na interface do usuário. Trate todos os dados fornecidos pelo usuário como um risco de segurança significativo para o aplicativo, o servidor e a rede. Saiba mais em Carregar arquivos no ASP.NET Core.
Carregar arquivos em um servidor com renderização do lado do servidor
Essa seção se aplica a componentes do servidor interativo nos Blazor Web Apps ou nos aplicativos Blazor Server.
O exemplo a seguir demonstra o carregamento de arquivos de um aplicativo do lado do servidor para um controlador de API Web de back-end em um aplicativo separado, possivelmente em um servidor separado.
No arquivo Program
do aplicativo do lado do servidor, adicione IHttpClientFactory e os serviços relacionados que permitam que o aplicativo crie instâncias HttpClient:
builder.Services.AddHttpClient();
Para saber mais, confira Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core.
Para os exemplos desta seção:
- A API Web é executada na URL:
https://localhost:5001
- O aplicativo do lado do servidor é executado na URL:
https://localhost:5003
No caso de testes, as URLs anteriores são configuradas nos arquivos Properties/launchSettings.json
dos projetos.
A classe UploadResult
a seguir mantém os resultados de um arquivos carregado. Quando um arquivo falha ao carregar no servidor, um código de erro é retornado em ErrorCode
para exibir ao usuário. Um nome de arquivo seguro é gerado no servidor para cada arquivo e retornado ao cliente em StoredFileName
para exibição. Os arquivos são chaveados entre o cliente e o servidor usando o nome do arquivo não seguro/não confiável em 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; }
}
Uma prática recomendada de segurança para aplicativos de produção é evitar o envio de mensagens de erro para clientes que possam revelar informações confidenciais sobre um aplicativo, servidor ou rede. O fornecimento de mensagens de erro detalhadas pode ajudar um usuário mal-intencionado a criar ataques em um aplicativo, servidor ou rede. O código de exemplo nesta seção enviará apenas um número de código de erro (int
) para exibição pelo componente do lado do cliente se ocorrer um erro do lado do servidor. Se um usuário precisar de assistência com um upload de arquivo, ele fornecerá o código de erro a fim ajudar a equipe de suporte na resolução de tíquetes de suporte sem nunca saber a causa exata do erro.
A classe LazyBrowserFileStream
a seguir define um tipo de fluxo personalizado que chama OpenReadStream lentamente pouco antes de os primeiros bytes do fluxo serem solicitados. O fluxo não é transmitido do navegador para o servidor até que a leitura do fluxo comece no .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();
}
O seguinte componente FileUpload2
:
- Permite que os usuários carreguem arquivos do cliente.
- Exibe o nome de arquivo não confiável/não seguro fornecido pelo cliente na interface do usuário. O nome do arquivo não confiável/não seguro é automaticamente codificado em HTML pelo Razor para exibição segura na interface do usuário.
Aviso
Não confiar em nomes de arquivo fornecidos por clientes para:
- Salvando o arquivo em um sistema ou serviço de arquivos.
- Exibição em UIs que não codificam nomes de arquivo automaticamente ou por meio do código do desenvolvedor.
Para obter mais informações sobre considerações de segurança ao carregar arquivos em um servidor, confira Carregar arquivos no 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 o componente limitar os uploads de arquivos a um único arquivo por vez ou se o componente adotar apenas a CSR, InteractiveWebAssembly
(renderização interativa do lado do cliente), o componente poderá evitar o uso do LazyBrowserFileStream
e usar um Stream. O seguinte demonstra as alterações do componente FileUpload2
:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remova a classe LazyBrowserFileStream
(LazyBrowserFileStream.cs
), pois ela não é usada.
Se o componente limitar os uploads de arquivos a um único arquivo por vez, o componente poderá evitar o uso do LazyBrowserFileStream
e usar um Stream. O seguinte demonstra as alterações do componente FileUpload2
:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remova a classe LazyBrowserFileStream
(LazyBrowserFileStream.cs
), pois ela não é usada.
O controlador a seguir no projeto de API Web salva arquivos carregados do cliente.
Importante
O controlador nesta seção destina-se a ser usado em um projeto de API Web separado do aplicativo Blazor. A API Web deve atenuar os ataques XSRF/CSRF (solicitação intersite forjada) se os usuários do upload de arquivo forem autenticados.
Observação
Não há suporte nativo para valores de formulário de associação com o atributo [FromForm]
em APIs mínimas no ASP.NET Core no .NET 6. Portanto, o exemplo do controlador Filesave
a seguir não pode ser convertido para usar APIs Mínimas. O suporte à associação em valores de formulário com APIs mínimas está disponível no ASP.NET Core no .NET 7 ou posterior.
Para usar o código a seguir, crie uma pasta Development/unsafe_uploads
na raiz do projeto de API Web para o aplicativo em execução no ambiente de Development
.
Como o exemplo usa o ambiente do aplicativo como parte do caminho em que os arquivos são salvos, pastas adicionais serão exigidas se outros ambientes forem usados em testes e produção. Por exemplo, crie uma pasta Staging/unsafe_uploads
para o ambiente Staging
. Crie uma pasta Production/unsafe_uploads
para o ambiente Production
.
Aviso
O exemplo salva arquivos sem verificar seu conteúdo, e as diretrizes neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparo e produção, desabilite a permissão de execução na pasta de upload e examine arquivos com uma API verificadora de antivírus/antimalware imediatamente após o upload. Saiba mais em Carregar arquivos no 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);
}
}
No código anterior, GetRandomFileName é chamado para gerar um nome de arquivo seguro. Nunca confie no nome do arquivo fornecido pelo navegador, pois um invasor cibernético pode escolher o nome de um arquivo já existente para substituí-lo ou enviar um caminho que tente gravar fora do aplicativo.
O aplicativo do servidor deve registrar os serviços do controlador e os pontos de extremidade do controlador de mapa. Para obter mais informações, confira Roteamento para ações do controlador no ASP.NET Core.
Carregar arquivos em um servidor com CSR (renderização do lado do cliente)
Esta seção se aplica a componentes da CSR (renderização de lado do cliente) em Blazor Web Apps ou aplicativos Blazor WebAssembly.
O exemplo a seguir demonstra o upload de arquivos em um controlador da API Web de back-end em um aplicativo separado, possivelmente em um servidor separado, de um componente em um Blazor Web App que adota a CSR ou um componente em um aplicativo Blazor WebAssembly.
A classe UploadResult
a seguir mantém os resultados de um arquivos carregado. Quando um arquivo falha ao carregar no servidor, um código de erro é retornado em ErrorCode
para exibir ao usuário. Um nome de arquivo seguro é gerado no servidor para cada arquivo e retornado ao cliente em StoredFileName
para exibição. Os arquivos são chaveados entre o cliente e o servidor usando o nome do arquivo não seguro/não confiável em 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; }
}
Observação
A classe UploadResult
anterior pode ser compartilhada entre os projetos baseados no cliente e no servidor. Quando os projetos do lado do cliente e do servidor compartilharem a classe, adicione uma importação ao arquivo _Imports.razor
de cada projeto para o projeto compartilhado. Por exemplo:
@using BlazorSample.Shared
O seguinte componente FileUpload2
:
- Permite que os usuários carreguem arquivos do cliente.
- Exibe o nome de arquivo não confiável/não seguro fornecido pelo cliente na interface do usuário. O nome do arquivo não confiável/não seguro é automaticamente codificado em HTML pelo Razor para exibição segura na interface do usuário.
Uma prática recomendada de segurança para aplicativos de produção é evitar o envio de mensagens de erro para clientes que possam revelar informações confidenciais sobre um aplicativo, servidor ou rede. O fornecimento de mensagens de erro detalhadas pode ajudar um usuário mal-intencionado a criar ataques em um aplicativo, servidor ou rede. O código de exemplo nesta seção enviará apenas um número de código de erro (int
) para exibição pelo componente do lado do cliente se ocorrer um erro do lado do servidor. Se um usuário precisar de assistência com um upload de arquivo, ele fornecerá o código de erro a fim ajudar a equipe de suporte na resolução de tíquetes de suporte sem nunca saber a causa exata do erro.
Aviso
Não confiar em nomes de arquivo fornecidos por clientes para:
- Salvando o arquivo em um sistema ou serviço de arquivos.
- Exibição em UIs que não codificam nomes de arquivo automaticamente ou por meio do código do desenvolvedor.
Para obter mais informações sobre considerações de segurança ao carregar arquivos em um servidor, confira Carregar arquivos no ASP.NET Core.
No projeto principal do Blazor Web App, adicione IHttpClientFactory e os serviços relacionados no arquivo Program
do projeto:
builder.Services.AddHttpClient();
Os serviços HttpClient
devem ser adicionados ao projeto principal porque o componente do lado do cliente é pré-renderizado no servidor. Se você desabilitar a pré-renderização para o componente a seguir, não será necessário fornecer os serviços HttpClient
no aplicativo principal, nem adicionar a linha anterior ao projeto principal.
Para obter mais informações sobre como adicionar serviços HttpClient
a um aplicativo ASP.NET Core, consulte Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core.
O projeto cliente (.Client
) de um Blazor Web App também deve registrar um HttpClient para solicitações HTTP POST para um controlador de API Web de back-end. Confirme ou adicione o seguinte ao arquivo do projeto Program
do cliente:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
O exemplo anterior define o endereço base com builder.HostEnvironment.BaseAddress
(IWebAssemblyHostEnvironment.BaseAddress), que obtém o endereço base do aplicativo e é normalmente derivado do valor href
da tag <base>
na página do host. Se estiver chamando uma API Web externa, defina o URI como o endereço base da API Web.
Especifique o atributo de modo de renderização WebAssembly Interativo na parte superior do seguinte componente em um Blazor Web App:
@rendermode InteractiveWebAssembly
FileUpload2.razor
:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var response = await Http.PostAsync("/Filesave", content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName);
if (result is null)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result = new();
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string Name { get; set; }
}
}
O controlador a seguir no projeto do lado do servidor salva arquivos carregados do cliente.
Observação
Não há suporte nativo para valores de formulário de associação com o atributo [FromForm]
em APIs mínimas no ASP.NET Core no .NET 6. Portanto, o exemplo do controlador Filesave
a seguir não pode ser convertido para usar APIs Mínimas. O suporte à associação em valores de formulário com APIs mínimas está disponível no ASP.NET Core no .NET 7 ou posterior.
Para usar o código a seguir, crie uma pasta Development/unsafe_uploads
na raiz do projeto do lado do servidor para o aplicativo em execução no ambiente de Development
.
Como o exemplo usa o ambiente do aplicativo como parte do caminho em que os arquivos são salvos, pastas adicionais serão exigidas se outros ambientes forem usados em testes e produção. Por exemplo, crie uma pasta Staging/unsafe_uploads
para o ambiente Staging
. Crie uma pasta Production/unsafe_uploads
para o ambiente Production
.
Aviso
O exemplo salva arquivos sem verificar seu conteúdo, e as diretrizes neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparo e produção, desabilite a permissão de execução na pasta de upload e examine arquivos com uma API verificadora de antivírus/antimalware imediatamente após o upload. Saiba mais em Carregar arquivos no ASP.NET Core.
No exemplo a seguir, atualize o namespace do projeto compartilhado para corresponder ao projeto compartilhado se este estiver fornecendo a classe UploadResult
.
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);
}
}
No código anterior, GetRandomFileName é chamado para gerar um nome de arquivo seguro. Nunca confie no nome do arquivo fornecido pelo navegador, pois um invasor cibernético pode escolher o nome de um arquivo já existente para substituí-lo ou enviar um caminho que tente gravar fora do aplicativo.
O aplicativo do servidor deve registrar os serviços do controlador e os pontos de extremidade do controlador de mapa. Para obter mais informações, confira Roteamento para ações do controlador no ASP.NET Core.
Cancelar um upload de arquivo
Um componente de upload de arquivo pode detectar quando um usuário cancelou um upload usando um CancellationToken ao chamar IBrowserFile.OpenReadStream ou StreamReader.ReadAsync.
Crie um CancellationTokenSource para o componente InputFile
. No início do método OnInputFileChange
, verifique se um upload anterior está em andamento.
Se um upload de arquivo estiver em andamento:
- Chame Cancel no upload anterior.
- Crie um CancellationTokenSource para o próximo upload e transmita o CancellationTokenSource.Token a OpenReadStream ou ReadAsync.
Carregar arquivos do lado do cliente com progresso
O exemplo a seguir demonstra como carregar arquivos em um aplicativos do lado do servidor com progresso de carregamento exibido para o usuário.
Para usar o exemplo abaixo em um aplicativo de teste:
- Crie uma pasta para salvar arquivos carregados para o ambiente de
Development
:Development/unsafe_uploads
. - Configure o tamanho máximo do arquivo (
maxFileSize
, 15 KB no exemplo a seguir) e o número máximo de arquivos permitidos (maxAllowedFiles
, 3 no exemplo a seguir). - Defina o buffer como um valor diferente (10 KB no exemplo a seguir), se desejado, para aumentar a granularidade no relatório de progresso. Não recomendamos usar um buffer maior que 30 KB devido a problemas de desempenho e segurança.
Aviso
O exemplo salva arquivos sem verificar seu conteúdo, e as diretrizes neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparo e produção, desabilite a permissão de execução na pasta de upload e examine arquivos com uma API verificadora de antivírus/antimalware imediatamente após o upload. Saiba mais em Carregar arquivos no 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;
}
}
Para obter mais informações, confira os seguintes recursos de API:
- FileStream: fornece um Stream para um arquivo, dando suporte a operações de leitura e gravação síncronas e assíncronas.
- FileStream.ReadAsync: o componente
FileUpload3
anterior lê o fluxo de forma assíncrona com ReadAsync. Não há suporte à leitura de um fluxo de forma síncrona com Read em componentes Razor.
Fluxos de arquivos
Com a interatividade do servidor, os dados do arquivo são transmitidos pela conexão SignalR ao código .NET no servidor à medida que o arquivo é lido.
RemoteBrowserFileStreamOptions permite configurar características de carregamento de arquivo.
Para um componente renderizado por WebAssembly, os dados do arquivo são transmitidos diretamente para o código .NET no navegador.
Pré-visualização de imagem de upload
Para uma pré-visualização de imagem do upload de imagens, comece adicionando um componente InputFile
com uma referência de componente e um manipulador OnChange
:
<InputFile @ref="inputFile" OnChange="ShowPreview" />
Adicione um elemento de imagem com uma referência de elemento, que serve como espaço reservado para a visualização da imagem:
<img @ref="previewImageElem" />
Adicione as referências associadas:
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}
No JavaScript, adicione uma função chamada com um elemento HTML input
e img
que executa o seguinte:
- Extrai o arquivo selecionado.
- Cria uma URL de objeto com
createObjectURL
. - Define um ouvinte de eventos para revogar a URL do objeto com
revokeObjectURL
depois que a imagem é carregada, ou seja, a memória não é vazada. - Define a origem do elemento
img
para exibir a imagem.
window.previewImage = (inputElem, imgElem) => {
const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
imgElem.src = url;
}
Por fim, usa um IJSRuntime injetado para adicionar o manipulador OnChange
que chama a função JavaScript:
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
O exemplo anterior serve para carregar uma única imagem. A abordagem pode ser expandida para dar suporte a imagens multiple
.
O componente FileUpload4
a seguir mostra o exemplo 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);
}
Carregar arquivos em um serviço externo
Em vez de um aplicativo tratar bytes de upload de arquivo e o servidor receber arquivos carregados, os clientes podem carregar arquivos diretamente em um serviço externo. O aplicativo pode processar com segurança os arquivos do serviço externo sob demanda. Essa abordagem protege o aplicativo e seu servidor contra ataques mal-intencionados e possíveis problemas de desempenho.
Considere uma abordagem que usa Arquivos do Azure, Armazenamento de Blobs do Azure ou um serviço de terceiros com os seguintes benefícios potenciais:
- Carregue arquivos do cliente diretamente para um serviço externo com uma biblioteca de clientes JavaScript ou API REST. Por exemplo, o Azure oferece as seguintes bibliotecas de cliente e APIs:
- Autorize uploads de usuário com um token SAS (assinatura de acesso compartilhado) delegado pelo usuário e gerado pelo aplicativo (lado do servidor) para cada upload de arquivo cliente. Por exemplo, o Azure oferece os seguintes recursos de SAS:
- Forneça redundância automática e backup de compartilhamento de arquivos.
- Limite os uploads com cotas. Observe que as cotas do Armazenamento de Blobs do Azure são definidas no nível da conta, não no nível do contêiner. No entanto, as cotas de Arquivos do Azure estão no nível de compartilhamento de arquivos e podem oferecer melhor controle sobre os limites de upload. Para obter mais informações, confira os documentos do Azure vinculados anteriormente nesta lista.
- Proteger arquivos com SSE (criptografia do lado do servidor).
Para obter mais informações sobre o Armazenamento de Blobs do Azure e os Arquivos do Azure, confira a documentação do Armazenamento do Azure.
Limite de tamanho de mensagem SignalR do lado do servidor
Os uploads de arquivo podem falhar antes mesmo de serem iniciados, quando o Blazor recupera dados sobre os arquivos que excedem o tamanho máximo de mensagem SignalR.
O SignalR define um limite de tamanho de mensagem que se aplica a cada mensagem que o Blazor recebe, e o componente InputFile transmite arquivos ao servidor em mensagens que respeitam o limite configurado. No entanto, a primeira mensagem, que indica o conjunto de arquivos a serem carregados, é enviada como uma única mensagem exclusiva. O tamanho da primeira mensagem pode exceder o limite de tamanho de mensagem SignalR. O problema não está relacionado ao tamanho dos arquivos e, sim, ao número de arquivos.
O erro registrado é semelhante ao seguinte:
Erro: conexão perdida com 'Erro: o servidor retornou um erro ao fechar: conexão fechada com um erro.'. e.log @ blazor.server.js:1
Ao carregar arquivos, é raro atingir o limite de tamanho da mensagem na primeira mensagem. Se o limite for atingido, o aplicativo poderá configurar HubOptions.MaximumReceiveMessageSize com um valor maior.
Para obter mais informações sobrea configuração SignalR e como definir MaximumReceiveMessageSize, confira Diretrizes do BlazorSignalR do ASP.NET Core.
Configuração máxima de invocações paralelas por hub de cliente
Blazor depende do MaximumParallelInvocationsPerClient conjunto como 1, que é o valor padrão.
Aumentar o valor leva a uma alta probabilidade que as operações CopyTo
geram System.InvalidOperationException: 'Reading is not allowed after reader was completed.'
. Para obter mais informações, consulte MaximumParallelInvocationsPerClient > 1 quebra o upload de arquivo no modo Blazor Server (dotnet/aspnetcore
nº 53951).
Solucionar problemas
A linha que chama IBrowserFile.OpenReadStream lança um System.TimeoutException:
System.TimeoutException: Did not receive any data in the allotted time.
Causas possíveis::
Usando o contêiner IoC (Inversão de controle) do Autofac em vez do contêiner interno de injeção de dependência ASP.NET Core. Para resolver o problema, defina DisableImplicitFromServicesParameters como
true
nas opções do hub do manipulador de circuito do lado do servidor. Para obter mais informações, consulte FileUpload: não recebeu nenhum dado no tempo alocado (dotnet/aspnetcore
#38842).Sem leitura do fluxo até a conclusão. Esta não é uma questão de estrutura. Intercepte a exceção e investigue-a ainda mais em seu ambiente/rede local.
- Usando a renderização do lado do servidor e chamando OpenReadStream em vários arquivos antes de lê-los até a conclusão. Para resolver o problema, use a classe
LazyBrowserFileStream
e a abordagem descrita na seção Carregar arquivos em um servidor com renderização do lado do servidor deste artigo.