Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Advertência
Esta versão do ASP.NET Core não é mais suportada. 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.
Este artigo explica como carregar ficheiros em Blazor com o componente InputFile.
Carregamentos de ficheiros
Advertência
Siga sempre as práticas recomendadas de segurança ao permitir que os usuários carreguem arquivos. Para obter mais informações, consulte Carregar arquivos no ASP.NET Core.
Use o InputFile componente para ler dados de arquivos do navegador em código .NET. O InputFile componente renderiza um elemento HTML <input> do tipo file para uploads de arquivo único. Adicione o multiple atributo para permitir que o usuário carregue vários arquivos de uma só vez.
A seleção de arquivos não é cumulativa ao usar um InputFile componente ou seu HTML <input type="file">subjacente, portanto, você não pode adicionar arquivos a uma seleção de arquivo existente. O componente sempre substitui a seleção inicial de arquivos do usuário, portanto, 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 ocorre o evento OnChange (change). Um InputFileChangeEventArgs fornece acesso à lista de arquivos selecionados 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 do elemento <input>_bl_2 é usado para o processamento interno de Blazor.
Para ler dados de um ficheiro selecionado pelo utilizador com um Stream que representa os bytes do ficheiro, chame IBrowserFile.OpenReadStream no ficheiro e leia a partir do fluxo retornado. Para obter mais informações, consulte a seção Fluxos de arquivos .
OpenReadStream impõe um tamanho máximo em bytes do seu Stream. A leitura de um arquivo ou vários arquivos maiores que 500 KB resulta em uma exceção. Esse limite impede que os desenvolvedores leiam acidentalmente arquivos grandes na memória. O maxAllowedSize parâmetro de OpenReadStream pode ser usado para especificar um tamanho maior, se necessário.
Além de processar um ficheiro pequeno, evite ler o fluxo de ficheiros de entrada diretamente na memória de uma só vez. Por exemplo, não copie todos os bytes do ficheiro para um MemoryStream ou leia todo o fluxo para um array de bytes de uma vez só. Essas abordagens podem resultar em desempenho degradado do aplicativo e risco potencial de negação de serviço (DoS), especialmente para componentes do lado do servidor. Em vez disso, considere adotar uma das seguintes abordagens:
- Copie o fluxo diretamente para um arquivo no disco sem lê-lo na memória. Observe que os aplicativos que Blazor executam código no servidor não podem acessar o sistema de arquivos do cliente diretamente.
- Carregue arquivos do cliente diretamente para um serviço externo. Para obter mais informações, consulte a seção Carregar arquivos para um serviço externo .
Nos exemplos a seguir, browserFile implementa IBrowserFile para representar um arquivo carregado. As implementações de trabalho para IBrowserFile são mostradas nos componentes de upload de arquivo mais adiante neste artigo.
Ao chamar o OpenReadStream, recomendamos passar um tamanho máximo de arquivo permitido no parâmetro maxAllowedSize no limite dos tamanhos de arquivo que se espera receber. O valor padrão é 500 KB. Os exemplos deste artigo usam uma variável de tamanho de arquivo máximo permitido ou constante nomeada maxFileSize , mas geralmente não mostram a configuração de um valor específico.
Suportado: A abordagem a seguir é recomendada porque o ficheiro é fornecido diretamente ao consumidor, um Stream que cria o ficheiro no caminho fornecido:
await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(fs);
Suportado: A seguinte abordagem é recomendada para Armazenamento de Blobs do Microsoft Azure porque o ficheiro Stream é fornecido diretamente para UploadBlobAsync:
await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream(maxFileSize));
Recomendado apenas para arquivos pequenos: A abordagem a seguir só é recomendada para arquivos pequenos porque o conteúdo do Stream arquivo é lido em uma MemoryStream memória (memoryStream), o que incorre em uma penalidade de desempenho e risco de DoS . Para obter um exemplo que demonstra essa técnica para salvar uma imagem em miniatura com um IBrowserFile em um banco de dados usando o Entity Framework Core (EF Core), consulte a seção Salvar arquivos pequenos diretamente em um banco de dados com EF Core mais adiante neste artigo.
using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
var smallFileByteArray = memoryStream.ToArray();
Não recomendado: A abordagem a seguir NÃO é recomendada porque o conteúdo do Stream arquivo é lido em uma String memória ():reader
var reader =
await new StreamReader(browserFile.OpenReadStream(maxFileSize)).ReadToEndAsync();
Não recomendado: A seguinte abordagem NÃO é recomendada para o Armazenamento de Blobs do Microsoft Azure porque o conteúdo do ficheiro é copiado para um Stream na memória (MemoryStream) antes de memoryStream chamar:UploadBlobAsync
var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));
Um componente que recebe um arquivo de imagem pode chamar o BrowserFileExtensions.RequestImageFileAsync método de conveniência no arquivo para redimensionar os dados de imagem dentro do tempo de execução JavaScript do navegador antes que a imagem seja transmitida para o aplicativo. Os casos de uso para chamadas RequestImageFileAsync são mais apropriados para Blazor WebAssembly aplicativos.
Limites de tamanho do ficheiro para leitura e carregamento
Para navegadores baseados no Chromium (por exemplo, Google Chrome e Microsoft Edge) que usam o protocolo HTTP/2, HTTPS e CORS, o cliente Blazor suporta a utilização da API Streams permitindo a realização de uploads de arquivos grandes com streaming de solicitações.
Sem um navegador Chromium, protocolo HTTP/2 ou HTTPS, do lado do cliente Blazor lê os bytes do arquivo em um buffer de matriz único ao converter os dados de JavaScript para C#, o que é limitado a 2 GB ou à memória disponível do dispositivo. Carregamentos de arquivos grandes podem falhar ao usar o componente InputFile para carregamentos do lado do cliente.
"O 'Client-side' Blazor lê os bytes do arquivo em um único buffer de array do JavaScript ao transferir os dados do JavaScript para C#, que é limitado a 2 GB ou à memória disponível do dispositivo." Carregamentos de arquivos grandes podem falhar ao usar o componente InputFile para carregamentos do lado do cliente. Recomendamos adotar o streaming de solicitações com o .NET 9 ou posterior.
Considerações de segurança
Evitar IBrowserFile.Size limites de tamanho de arquivo
Evite usar IBrowserFile.Size para impor um limite no tamanho do arquivo. Em vez de usar o tamanho de arquivo fornecido pelo cliente não seguro, especifique explicitamente o tamanho máximo do arquivo. O exemplo a seguir usa o tamanho máximo de arquivo atribuído a maxFileSize:
- var fileContent = new StreamContent(file.OpenReadStream(file.Size));
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Segurança do nome do ficheiro
Nunca use um nome de arquivo fornecido pelo cliente para salvar um arquivo no armazenamento físico. Crie um nome de arquivo seguro para o arquivo usando Path.GetRandomFileName() ou Path.GetTempFileName() para criar um caminho completo (incluindo o nome do arquivo) para armazenamento temporário.
Razor automaticamente HTML codifica valores de propriedade para exibição. O código a seguir é seguro de usar:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
Fora do Razor, sempre use HtmlEncode para codificar com segurança nomes de arquivo a partir da solicitação de um usuário.
Muitas implementações devem incluir uma verificação de que o arquivo existe; caso contrário, o arquivo será substituído por um arquivo com o mesmo nome. Forneça lógica adicional para atender às especificações do seu aplicativo.
Exemplos
Os exemplos a seguir demonstram o carregamento de vários arquivos em um componente. InputFileChangeEventArgs.GetMultipleFiles permite a leitura de vários ficheiros. Especifique o número máximo de arquivos para evitar que um usuário mal-intencionado carregue um número maior de arquivos do que o aplicativo espera. InputFileChangeEventArgs.File Permite ler o primeiro e único ficheiro se o carregamento do ficheiro não suportar vários ficheiros.
InputFileChangeEventArgs está no namespace Microsoft.AspNetCore.Components.Forms, que normalmente é um dos namespaces no ficheiro da aplicação _Imports.razor. Quando o namespace está presente no _Imports.razor arquivo, ele fornece acesso de membro da API aos componentes do aplicativo.
Os namespaces no _Imports.razor arquivo não são aplicados a arquivos C# (.cs). Os arquivos C# exigem uma diretiva explícita using na parte superior do arquivo de classe:
using Microsoft.AspNetCore.Components.Forms;
Para testar componentes de carregamento de arquivos, você pode 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 marcador de posição
{SIZE}representa o tamanho do arquivo em bytes (por exemplo,2097152para um arquivo de 2 MB). - O
{PATH}marcador de posição é o caminho e o ficheiro com extensão de ficheiro (por exemplo,D:/test_files/testfile2MB.txt).
Exemplo de carregamento de ficheiros do lado do servidor
Para usar o código a seguir, crie uma Development/unsafe_uploads pasta na raiz do aplicativo em execução no Development ambiente.
Como o exemplo usa o ambiente do aplicativo como parte do caminho onde os arquivos são salvos, pastas adicionais são necessárias se outros ambientes forem usados em testes e produção. Por exemplo, crie uma Staging/unsafe_uploads pasta para o Staging ambiente. Crie uma Production/unsafe_uploads pasta para o Production ambiente.
Advertência
O exemplo salva arquivos sem verificar seu conteúdo, e as orientações neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparação e produção, desative a permissão de execução na pasta de upload e digitalize arquivos com uma API de scanner antivírus/antimalware imediatamente após o upload. Para obter mais informações, consulte 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 = [];
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 Razor componente que envia um arquivo para um servidor ou serviço, consulte as seguintes seções:
- Carregar arquivos para um servidor com renderização do lado do cliente (CSR)
- Carregar ficheiros para um serviço externo
O componente pressupõe que o modo de renderização Interactive WebAssembly (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 = [];
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 para validação preliminar.
Nunca confie nos valores das propriedades anteriores, especialmente na Name propriedade 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. Para obter mais informações, consulte Carregar arquivos no ASP.NET Core.
Carregar arquivos para um servidor com renderização do lado do servidor
Esta seção se aplica aos componentes do Servidor Interativo em Blazor Web Apps ou Blazor Server aplicativos.
O exemplo a seguir demonstra o carregamento de ficheiros de um aplicativo do lado servidor para um controlador de API web backend num aplicativo separado, e potencialmente num servidor distinto.
No arquivo do aplicativo do lado servidor Program, adicione IHttpClientFactory e serviços relacionados que permitem ao aplicativo criar instâncias de HttpClient.
builder.Services.AddHttpClient();
Para obter mais informações, consulte Fazer solicitações HTTP usando IHttpClientFactory em ASP.NET Core.
Para os exemplos nesta secção:
- A API da Web é executada no URL:
https://localhost:5001 - O aplicativo do lado do servidor é executado na URL:
https://localhost:5003
Para testes, as URLs anteriores são configuradas nos arquivos de projeto Properties/launchSettings.json.
A classe a seguir UploadResult mantém o resultado de um arquivo carregado. Quando um ficheiro não é carregado no servidor, um código de erro é retornado em ErrorCode para exibição ao usuário. Um nome de arquivo seguro é gerado no servidor para cada arquivo e retornado ao cliente em StoredFileName para exibição. Os ficheiros são introduzidos entre o cliente e o servidor utilizando o nome de ficheiro não seguro/não fidedigno 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. Fornecer mensagens de erro detalhadas pode ajudar um usuário mal-intencionado a criar ataques a um aplicativo, servidor ou rede. O código de exemplo nesta seção só envia de volta 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 utilizador precisar de assistência com o upload de um ficheiro, ele fornece o código de erro ao pessoal de suporte para resolver o pedido de assistência, sem nunca saber a causa exata do erro.
A classe a seguir LazyBrowserFileStream define um tipo de fluxo personalizado que chama OpenReadStream preguiçosamente pouco antes dos 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 de arquivo não confiável/não seguro é automaticamente codificado em HTML por Razor para exibição segura na interface do utilizador.
Advertência
Não confie nos nomes de arquivo fornecidos pelos clientes para:
- Salvar o arquivo em um sistema de arquivos ou serviço.
- Exiba em interfaces do usuário que não codificam nomes de arquivo automaticamente ou por meio de código de desenvolvedor.
Para obter mais informações sobre considerações de segurança ao carregar arquivos em um servidor, consulte 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 = [];
private List<UploadResult> uploadResults = [];
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
int maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var stream = new LazyBrowserFileStream(file, maxFileSize);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
var client = ClientFactory.CreateClient();
using 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();
using 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();
using 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();
using 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 o upload de ficheiros a um único de cada vez ou se o componente adotar apenas a renderização do lado do cliente (CSR, InteractiveWebAssembly), o componente pode evitar o uso de LazyBrowserFileStream e utilizar um Stream. O seguinte demonstra as alterações para o FileUpload2 componente:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remova a LazyBrowserFileStream classe (LazyBrowserFileStream.cs), pois ela não é usada.
Se o componente limitar o upload de arquivos a um único arquivo de cada vez, o componente poderá evitar o uso do LazyBrowserFileStream e usar um Streamarquivo . O seguinte demonstra as alterações para o FileUpload2 componente:
- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
Remova a LazyBrowserFileStream classe (LazyBrowserFileStream.cs), pois ela não é usada.
O controlador a seguir no projeto de API da Web salva os arquivos carregados do cliente.
Importante
O controlador nesta seção destina-se ao uso em um projeto de API da Web separado do Blazor aplicativo. A API da Web deve mitigar os ataques de falsificação de solicitação entre sites (XSRF/CSRF) se os usuários de upload de arquivos forem autenticados.
Observação
A associação de valores de formulário com o atributo [FromForm] não é suportada nativamente para APIs mínimas no ASP.NET Core no .NET 6. Portanto, o exemplo de controlador a seguir Filesave não pode ser convertido para usar APIs mínimas. O suporte para vinculação de 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 Development/unsafe_uploads pasta na raiz do projeto de API da Web para o aplicativo em execução no Development ambiente.
Como o exemplo usa o ambiente do aplicativo como parte do caminho onde os arquivos são salvos, pastas adicionais são necessárias se outros ambientes forem usados em testes e produção. Por exemplo, crie uma Staging/unsafe_uploads pasta para o Staging ambiente. Crie uma Production/unsafe_uploads pasta para o Production ambiente.
Advertência
O exemplo salva arquivos sem verificar seu conteúdo, e as orientações neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparação e produção, desative a permissão de execução na pasta de upload e digitalize arquivos com uma API de scanner antivírus/antimalware imediatamente após o upload. Para obter mais informações, consulte 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 de arquivo fornecido pelo navegador, pois um ciberinvasor pode escolher um nome de arquivo existente que substitui um arquivo existente ou enviar um caminho que tenta gravar fora do aplicativo.
A aplicação de servidor deve registar serviços do controlador e mapear interfaces do controlador. Para obter mais informações, consulte Roteamento para ações do controlador no ASP.NET Core.
Fazer upload de ficheiros para um servidor com renderização do lado do cliente (CSR)
Esta seção se aplica a componentes renderizados do lado do cliente (CSR) em Blazor Web Apps ou Blazor WebAssembly aplicativos.
O exemplo a seguir demonstra o upload de ficheiros para um controlador de API web de backend num aplicativo separado, possivelmente num servidor separado, de um componente em um Blazor Web App que adota CSR ou de um componente em um aplicativo Blazor WebAssembly.
O exemplo adota o streaming de solicitações para um navegador baseado no Chromium (por exemplo, Google Chrome ou Microsoft Edge) com protocolo HTTP/2 e HTTPS. Se o streaming de solicitação não puder ser usado, Blazor regride graçadamente para a Fetch API sem streaming de solicitação. Para obter mais informações, consulte a seção Limites de leitura e carregamento do tamanho do arquivo .
A classe a seguir UploadResult mantém o resultado de um arquivo carregado. Quando um ficheiro não é carregado no servidor, um código de erro é retornado em ErrorCode para exibição ao usuário. Um nome de arquivo seguro é gerado no servidor para cada arquivo e retornado ao cliente em StoredFileName para exibição. Os ficheiros são introduzidos entre o cliente e o servidor utilizando o nome de ficheiro não seguro/não fidedigno 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 anterior UploadResult pode ser compartilhada entre projetos baseados em cliente e servidor. Quando os projetos cliente e servidor partilham a classe, adicione uma importação ao arquivo _Imports.razor de cada projeto para o projeto partilhado. 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 de arquivo não confiável/não seguro é automaticamente codificado em HTML por Razor para exibição segura na interface do utilizador.
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. Fornecer mensagens de erro detalhadas pode ajudar um usuário mal-intencionado a criar ataques a um aplicativo, servidor ou rede. O código de exemplo nesta seção só envia de volta 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 utilizador precisar de assistência com o upload de um ficheiro, ele fornece o código de erro ao pessoal de suporte para resolver o pedido de assistência, sem nunca saber a causa exata do erro.
Advertência
Não confie nos nomes de arquivo fornecidos pelos clientes para:
- Salvar o arquivo em um sistema de arquivos ou serviço.
- Exiba em interfaces do usuário que não codificam nomes de arquivo automaticamente ou por meio de código de desenvolvedor.
Para obter mais informações sobre considerações de segurança ao carregar arquivos em um servidor, consulte Carregar arquivos no ASP.NET Core.
No projeto do Blazor Web App servidor, adicione IHttpClientFactory e serviços relacionados no arquivo Program do projeto:
builder.Services.AddHttpClient();
Os HttpClient serviços devem ser adicionados ao projeto do servidor 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 HttpClient serviços no projeto de servidor e não precisará adicionar a linha anterior ao projeto de servidor.
Para obter mais informações sobre como adicionar HttpClient serviços 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 deve também registar um HttpClient para pedidos POST HTTP a um controlador de API web de back-end. Confirme ou adicione o seguinte ao arquivo do Program projeto 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 para o aplicativo e normalmente é derivado do valor <base> da tag href na página de host. Se você estiver chamando uma API da Web externa, defina o URI como o endereço base da API da Web.
Um aplicativo independente Blazor WebAssembly que carrega ficheiros para uma API web de um servidor separado usa um nome HttpClient ou define o registo de serviço padrão HttpClient para apontar para o ponto de extremidade da API web. No exemplo a seguir, onde a API da Web é hospedada localmente na porta 5001, o endereço base é https://localhost:5001:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri("https://localhost:5001") });
Em um Blazor Web App, adicione o Microsoft.AspNetCore.Components.WebAssembly.Http namespace às diretivas do componente:
@using Microsoft.AspNetCore.Components.WebAssembly.Http
FileUpload2.razor:
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Net
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
request.SetBrowserRequestStreamingEnabled(true);
request.Content = content;
using var response = await Http.SendAsync(request);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
if (newUploadResults is not null)
{
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
}
shouldRender = true;
}
private static bool FileUpload(IList<UploadResult> uploadResults,
string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();
if (!result.Uploaded)
{
logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
result.ErrorCode = 5;
}
return result.Uploaded;
}
private class File
{
public string? Name { get; set; }
}
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger
<PageTitle>File Upload 2</PageTitle>
<h1>File Upload Example 2</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, Logger,
out var result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}
@code {
private List<File> files = [];
private List<UploadResult> uploadResults = [];
private int maxAllowedFiles = 3;
private bool shouldRender;
protected override bool ShouldRender() => shouldRender;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 6): {Message}",
file.Name, ex.Message);
uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false
});
}
}
}
if (upload)
{
using 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)
{
using 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)
{
using 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)
{
using 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 seguinte controlador no projeto do lado do servidor salva os arquivos enviados pelo cliente.
Observação
A associação de valores de formulário com o atributo [FromForm] não é suportada nativamente para APIs mínimas no ASP.NET Core no .NET 6. Portanto, o exemplo de controlador a seguir Filesave não pode ser convertido para usar APIs mínimas. O suporte para vinculação de 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 Development/unsafe_uploads pasta na raiz do projeto do lado do servidor para o aplicativo em execução no Development ambiente.
Como o exemplo usa o ambiente do aplicativo como parte do caminho onde os arquivos são salvos, pastas adicionais são necessárias se outros ambientes forem usados em testes e produção. Por exemplo, crie uma Staging/unsafe_uploads pasta para o Staging ambiente. Crie uma Production/unsafe_uploads pasta para o Production ambiente.
Advertência
O exemplo salva arquivos sem verificar seu conteúdo, e as orientações neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparação e produção, desative a permissão de execução na pasta de upload e digitalize arquivos com uma API de scanner antivírus/antimalware imediatamente após o upload. Para obter mais informações, consulte Carregar arquivos no ASP.NET Core.
No exemplo a seguir para um aplicativo hospedado Blazor WebAssembly ou onde um projeto compartilhado é usado para fornecer a UploadResult classe, adicione o namespace do projeto compartilhado:
using BlazorSample.Shared;
Recomendamos o uso de um namespace para o seguinte controlador (por exemplo: namespace BlazorSample.Controllers).
Controllers/FilesaveController.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
[ApiController]
[Route("[controller]")]
public class FilesaveController(
IHostEnvironment env, ILogger<FilesaveController> logger)
: ControllerBase
{
[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = [];
foreach (var file in files)
{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);
if (filesProcessed < maxAllowedFiles)
{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is " +
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length, maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage = Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);
logger.LogInformation("{FileName} saved at {Path}",
trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName = trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err: 3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}
filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the " +
"request exceeded the allowed {Count} of files (Err: 4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}
return new CreatedResult(resourcePath, uploadResults);
}
}
No código anterior, GetRandomFileName é chamado para gerar um nome de arquivo seguro. Nunca confie no nome de arquivo fornecido pelo navegador, pois um ciberinvasor pode escolher um nome de arquivo existente que substitui um arquivo existente ou enviar um caminho que tenta gravar fora do aplicativo.
A aplicação de servidor deve registar serviços do controlador e mapear interfaces do controlador. Para obter mais informações, consulte Roteamento para ações do controlador no ASP.NET Core. Recomendamos adicionar serviços de controlador com AddControllersWithViews para mitigar automaticamente os ataques de falsificação de solicitação entre sites (XSRF/CSRF) para utilizadores autenticados. Se você apenas usar AddControllers, o antifalsificação não será ativado automaticamente. Para obter mais informações, consulte Roteamento para ações do controlador no ASP.NET Core.
A configuração de Solicitações entre Origens (CORS) no servidor é necessária para transmissão de pedidos quando o servidor está hospedado numa origem diferente, e um pedido de pré-verificação é sempre feito pelo cliente. Na configuração do ficheiro de serviço do servidor Program (o projeto de servidor de um Blazor Web App ou a API web do servidor de back-end de uma aplicação Blazor WebAssembly), a seguinte política CORS padrão é adequada para testar com os exemplos neste artigo. O cliente faz o pedido local a partir da porta 5003. Altere o número da porta para corresponder à porta do aplicativo cliente que você está usando:
Configure Solicitações de Origem Cruzada (CORS) no servidor. Na configuração do ficheiro de serviço do servidor Program (o projeto de servidor de um Blazor Web App ou a API web do servidor de back-end de uma aplicação Blazor WebAssembly), a seguinte política CORS padrão é adequada para testar com os exemplos neste artigo. O cliente faz o pedido local a partir da porta 5003. Altere o número da porta para corresponder à porta do aplicativo cliente que você está usando:
Configure Solicitações de Origem Cruzada (CORS) no servidor. Na configuração de serviço do ficheiro Program da API Web do servidor de back-end, a seguinte política padrão de CORS é adequada para testes com os exemplos no artigo. O cliente faz o pedido local a partir da porta 5003. Altere o número da porta para corresponder à porta do aplicativo cliente que você está usando:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
Depois de chamar UseHttpsRedirection no arquivo Program, chame UseCors para adicionar o middleware CORS:
app.UseCors();
Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core.
Configure o tamanho máximo do corpo da solicitação do servidor e os limites de comprimento do corpo de várias partes se os limites restringirem o tamanho do carregamento.
Para o Kestrel servidor, defina MaxRequestBodySize (padrão: 30.000.000 bytes) e FormOptions.MultipartBodyLengthLimit (padrão: 134.217.728 bytes). Defina a maxFileSize variável no componente e no controlador com o mesmo valor.
Na seguinte Program configuração de arquivo Kestrel (o projeto de servidor de um Blazor Web App ou a API Web do servidor backend de uma Blazor WebAssembly aplicação), o espaço reservado {LIMIT} é o limite em bytes:
using Microsoft.AspNetCore.Http.Features;
...
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = {LIMIT};
});
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = {LIMIT};
});
Cancelar o carregamento de um ficheiro
Um componente de carregamento de arquivo pode detetar quando um usuário cancelou um carregamento usando um CancellationToken ao chamar o IBrowserFile.OpenReadStream ou StreamReader.ReadAsync.
Crie um CancellationTokenSource para o InputFile componente. No início do OnInputFileChange método, verifique se um carregamento anterior está em andamento.
Se o carregamento de um ficheiro estiver em curso:
- Ligue Cancel para o upload anterior.
- Crie um novo CancellationTokenSource para o próximo upload e passe o CancellationTokenSource.Token para OpenReadStream ou ReadAsync.
Realize o upload de arquivos no servidor com acompanhamento de progresso
O exemplo a seguir demonstra como carregar arquivos em um aplicativo do lado do servidor com o progresso do carregamento exibido para o usuário.
Para usar o exemplo a seguir em um aplicativo de teste:
-
Crie uma pasta para salvar os arquivos carregados para o
Developmentambiente: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 para um valor diferente (10 KB no exemplo a seguir), se desejado, para aumentar a granularidade nos relatórios de progresso. Não recomendamos o uso de um buffer maior que 30 KB devido a questões de desempenho e segurança.
Advertência
O exemplo salva arquivos sem verificar seu conteúdo, e as orientações neste artigo não levam em conta as práticas recomendadas de segurança adicionais para arquivos carregados. Em sistemas de preparação e produção, desative a permissão de execução na pasta de upload e digitalize arquivos com uma API de scanner antivírus/antimalware imediatamente após o upload. Para obter mais informações, consulte 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 = [];
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, consulte os seguintes recursos de API:
- FileStream: Fornece um Stream para um arquivo, suportando operações de leitura e gravação síncronas e assíncronas.
-
FileStream.ReadAsync: O componente anterior
FileUpload3lê o fluxo de forma assíncrona com ReadAsync. A leitura de um fluxo de forma síncrona com Read não é suportada nos Razor componentes.
Fluxos de arquivos
Com a interatividade do servidor, os dados do arquivo são transmitidos pela conexão para o código .NET SignalR no servidor enquanto o arquivo é lido.
RemoteBrowserFileStreamOptions Permite configurar as características de carregamento de ficheiros.
Para um componente renderizado pelo WebAssembly, os dados do arquivo são transmitidos diretamente para o código .NET dentro do navegador.
Carregar pré-visualização de imagens
Para pré-visualizar o upload de imagens, comece por adicionar 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;
}
Em JavaScript, adicione uma função com elementos HTML input e img que execute 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
revokeObjectURLapós o carregamento da imagem, evitando assim vazamentos de memória. - Define a origem do
imgelemento 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;
}
Finalmente, utilize uma injeção IJSRuntime para adicionar o OnChange controlador que chama a função JavaScript.
@inject IJSRuntime JS
...
@code {
...
private async Task ShowPreview() => await JS.InvokeVoidAsync(
"previewImage", inputFile!.Element, previewImageElem);
}
O exemplo anterior é para carregar uma única imagem. A abordagem pode ser expandida para dar suporte a multiple imagens.
O componente a seguir FileUpload4 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);
}
Salve pequenos arquivos diretamente em um banco de dados com EF Core
Muitos aplicativos ASP.NET Core usam o Entity Framework Core (EF Core) para gerenciar operações de banco de dados. Salvar miniaturas e avatares diretamente no banco de dados é um requisito comum. Esta seção demonstra uma abordagem geral que pode ser aprimorada para aplicativos de produção.
O seguinte padrão:
- Baseia-se no aplicativo tutorial doBlazor banco de dados de filmes.
- Pode ser aprimorado com código adicional para comentários de validação de tamanho de arquivo e tipo de conteúdo.
- Incorre em penalidade de desempenho e perigo de DoS. Pese cuidadosamente o risco ao ler qualquer arquivo na memória e considere abordagens alternativas, especialmente para arquivos maiores. Abordagens alternativas incluem salvar arquivos diretamente no disco ou em um serviço de terceiros para verificações antivírus/antimalware, processamento adicional e atendimento a clientes.
Para que o exemplo a seguir funcione num Blazor Web App (.NET 8 ou posterior), o componente deve adotar um modo de renderização interativo (por exemplo, @rendermode InteractiveServer) para chamar HandleSelectedThumbnail num(a) InputFile ficheiro de componente alterado (OnChange parâmetro/evento).
Blazor Server Os componentes do aplicativo são sempre interativos e não exigem um modo de renderização.
No exemplo a seguir, uma pequena miniatura (<= 100 KB) em um IBrowserFile é salva em um banco de dados com EF Core. Se um arquivo não for selecionado pelo usuário para o InputFile componente, uma miniatura padrão será salva no banco de dados.
A miniatura padrão (default-thumbnail.jpg) está no diretório raiz do projeto com uma configuração Copiar para o diretório de saída de Copiar se mais recente:
O Movie modelo (Movie.cs) tem uma propriedade (Thumbnail) para armazenar os dados da imagem de miniatura:
[Column(TypeName = "varbinary(MAX)")]
public byte[]? Thumbnail { get; set; }
Os dados de imagem são armazenados como bytes no banco de dados como varbinary(MAX). O aplicativo base-64 codifica os bytes para exibição porque os dados codificados em base-64 são aproximadamente um terço maiores do que os bytes brutos da imagem, portanto, os dados de imagem base-64 exigem armazenamento adicional do banco de dados e reduzem o desempenho das operações de leitura/gravação do banco de dados.
Os componentes que exibem a miniatura passam dados da imagem para o img atributo da src tag como JPEG, dados codificados em base 64:
<img src="data:image/jpeg;base64,@Convert.ToBase64String(movie.Thumbnail)"
alt="User thumbnail" />
No componente a seguir Create , um upload de imagem é processado. Você pode aprimorar ainda mais o exemplo com a validação personalizada para tipo e tamanho de arquivo usando as abordagens em ASP.NET validação de formulários principaisBlazor. Para ver o componente completo Create sem o código de upload de miniatura no exemplo a seguir, consulte a BlazorWebAppMovies aplicação de exemplo no Blazor repositório de exemplos do GitHub.
Components/Pages/MoviePages/Create.razor:
@page "/movies/create"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using BlazorWebAppMovies.Models
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory
@inject NavigationManager NavigationManager
...
<div class="row">
<div class="col-md-4">
<EditForm method="post" Model="Movie" OnValidSubmit="AddMovie"
FormName="create" Enhance>
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert"/>
...
<div class="mb-3">
<label for="thumbnail" class="form-label">Thumbnail:</label>
<InputFile id="thumbnail" OnChange="HandleSelectedThumbnail"
class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</EditForm>
</div>
</div>
...
@code {
private const long maxFileSize = 102400;
private IBrowserFile? browserFile;
[SupplyParameterFromForm]
private Movie Movie { get; set; } = new();
private void HandleSelectedThumbnail(InputFileChangeEventArgs e)
{
browserFile = e.File;
}
private async Task AddMovie()
{
using var context = DbFactory.CreateDbContext();
if (browserFile?.Size > 0 && browserFile?.Size <= maxFileSize)
{
using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
Movie.Thumbnail = memoryStream.ToArray();
}
else
{
Movie.Thumbnail = File.ReadAllBytes(
$"{AppDomain.CurrentDomain.BaseDirectory}default_thumbnail.jpg");
}
context.Movie.Add(Movie);
await context.SaveChangesAsync();
NavigationManager.NavigateTo("/movies");
}
}
A mesma abordagem seria adotada no componente Edit com um modo de renderização interativo, caso os usuários tivessem permissão para editar a imagem em miniatura de um filme.
Carregar ficheiros para um serviço externo
Em vez de um aplicativo manipular bytes de upload de arquivos e o servidor do aplicativo receber arquivos carregados, os clientes podem carregar arquivos diretamente para 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 use Arquivos do Azure, Armazenamento de Blob 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 cliente JavaScript ou REST API. Por exemplo, o Azure oferece as seguintes bibliotecas de cliente e APIs:
- Autorize carregamentos de usuários com um token SAS (assinatura de acesso compartilhado) delegado pelo usuário gerado pelo aplicativo (lado do servidor) para cada carregamento de arquivo cliente. Por exemplo, o Azure oferece os seguintes recursos SAS:
- Forneça redundância automática e backup de compartilhamento de arquivos.
- Limite os carregamentos com quotas. 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 dos Arquivos do Azure estão no nível de compartilhamento de arquivos e podem fornecer um melhor controle sobre os limites de carregamento. Para obter mais informações, consulte os documentos do Azure vinculados anteriormente nesta lista.
- Proteja os ficheiros com encriptação do lado do servidor (SSE).
Para obter mais informações sobre o Armazenamento de Blobs do Azure e os Arquivos do Azure, consulte a documentação do Armazenamento do Azure.
Limite de tamanho de mensagem do lado SignalR do servidor
Os carregamentos de ficheiros podem falhar mesmo antes de começarem, quando Blazor recupera dados sobre os ficheiros que excedem o tamanho máximo SignalR da mensagem.
SignalR define um limite de tamanho de mensagem que se aplica a cada mensagem Blazor recebida e o InputFile componente transmite arquivos para o 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 da SignalR mensagem. O problema não está relacionado com o tamanho dos ficheiros, está relacionado com o número de ficheiros.
O erro registrado é semelhante ao seguinte:
Erro: Conexão desconectada com o erro 'Erro: O servidor retornou um erro ao fechar: Conexão fechada com um erro.'. e.log @ blazor.server.js:1
Ao carregar ficheiros, é 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 sobre SignalR configuração e como definir MaximumReceiveMessageSize, consulte ASP.NET Orientação principalBlazorSignalR.
Máximo de invocações paralelas por configuração de hub cliente
Blazor depende de MaximumParallelInvocationsPerClient definido como 1, que é o valor padrão.
Aumentar o valor leva a uma alta probabilidade de que as operações CopyTo lancem System.InvalidOperationException: 'Reading is not allowed after reader was completed.'. Para mais informações, veja MaximumParallelInvocationsPerClient > 1 causa problemas no upload de arquivos no modo Blazor Server (dotnet/aspnetcore #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 Autofac Inversion of Control (IoC) em vez do contêiner interno de injeção de dependência do ASP.NET Core no .NET 8 ou anterior. Para resolver o problema, defina DisableImplicitFromServicesParameters como
truenas opções de 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).Não ler o fluxo de dados até ao fim. Esta não é uma questão de enquadramento. Intercepte a exceção e investigue-a mais aprofundadamente na sua rede/local.
- Usar renderização do lado do servidor e chamar OpenReadStream em vários ficheiros antes de os ler na totalidade. Para resolver o problema, use a classe e a
LazyBrowserFileStreamabordagem descritas na seção Carregar arquivos para um servidor com renderização do lado do servidor deste artigo.