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.
Por Rutger Storm
O ASP.NET Core suporta o upload de um ou mais arquivos usando a vinculação de modelo em buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Visualizar ou descarregar amostra de código (como descarregar)
Considerações de segurança
Tenha cuidado ao fornecer aos usuários a capacidade de carregar arquivos para um servidor. Os ciberatacantes podem tentar:
- Execute ataques de negação de serviço .
- Carregue vírus ou malware.
- Comprometa redes e servidores de outras formas.
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
- Carregue ficheiros para uma área de carregamento de ficheiros dedicada, de preferência para uma unidade que não seja do sistema. Uma localização dedicada facilita a imposição de restrições de segurança aos ficheiros carregados. Desative as permissões de execução no local de carregamento do arquivo.†
- Não persista os arquivos carregados na mesma árvore de diretórios do aplicativo.†
- Use um nome de arquivo seguro determinado pelo aplicativo. Não use um nome de arquivo fornecido pelo usuário ou o nome de arquivo não confiável do arquivo carregado.† HTML codificar o nome de arquivo não confiável ao exibi-lo. Por exemplo, registrar o nome do arquivo ou exibir na interface do usuário (Razor automaticamente HTML codifica a saída).
- Permita apenas extensões de arquivo aprovadas para a especificação de design do aplicativo.†
- Verifique se as verificações do lado do cliente são executadas no servidor.† As verificações do lado do cliente são fáceis de contornar.
- Verifique o tamanho de um ficheiro carregado. Defina um limite máximo de tamanho para evitar carregamentos grandes.†
- Quando os arquivos não devem ser substituídos por um arquivo carregado com o mesmo nome, verifique o nome do arquivo em relação ao banco de dados ou ao armazenamento físico antes de carregá-lo.
- Execute um verificador de vírus/malware no conteúdo carregado antes que o arquivo seja armazenado.
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Warning
Carregar código malicioso para um sistema é frequentemente o primeiro passo para executar código que pode:
- Ganhe completamente o controle de um sistema.
- Sobrecarregue um sistema com o resultado de que o sistema falha.
- Comprometa os dados do usuário ou do sistema.
- Aplique graffiti a uma interface do usuário pública.
Para obter informações sobre como reduzir vulnerabilidades ao aceitar arquivos de usuários, consulte os seguintes recursos:
Para obter mais informações sobre como implementar medidas de segurança, incluindo exemplos do aplicativo de exemplo, consulte a seção Validação .
Cenários de armazenamento
As opções comuns de armazenamento para arquivos incluem:
Database
- Para carregamentos de arquivos pequenos , um banco de dados geralmente é mais rápido do que as opções de armazenamento físico (sistema de arquivos ou compartilhamento de rede).
- Um banco de dados geralmente é mais conveniente do que as opções de armazenamento físico porque a recuperação de um registro de banco de dados para dados do usuário pode fornecer simultaneamente o conteúdo do arquivo (por exemplo, uma imagem de avatar).
- Um banco de dados é potencialmente mais barato do que usar um serviço de armazenamento de dados em nuvem.
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
- Para carregamentos de ficheiros grandes:
- Os limites do banco de dados podem restringir o tamanho do upload.
- O armazenamento físico geralmente é menos econômico do que o armazenamento em um banco de dados.
- O armazenamento físico é potencialmente menos dispendioso do que a utilização de um serviço de armazenamento de dados na nuvem.
- O processo do aplicativo deve ter permissões de leitura e gravação no local de armazenamento. Nunca conceda permissão de execução.
- Para carregamentos de ficheiros grandes:
Serviço de armazenamento de dados na nuvem, por exemplo, Azure Blob Storage.
- Os serviços geralmente oferecem escalabilidade e resiliência aprimoradas em relação a soluções locais que geralmente estão sujeitas a pontos únicos de falha.
- Os serviços são potencialmente mais baratos em cenários de grandes infraestruturas de armazenamento.
Para obter mais informações, consulte Guia de início rápido: usar o .NET para criar um blob no armazenamento de objetos.
Ficheiros pequenos e grandes
A definição de arquivos pequenos e grandes depende dos recursos de computação disponíveis. Os aplicativos devem comparar a abordagem de armazenamento usada para garantir que eles possam lidar com os tamanhos esperados. Avalie o desempenho da memória, CPU, disco e banco de dados.
Embora não seja possível fornecer limites específicos sobre o que é pequeno versus grande para sua implantação, aqui estão alguns dos padrões relacionados ao ASP.NET Core para FormOptions (documentação da API):
- Por padrão,
HttpRequest.Formnão armazena em buffer todo o corpo da solicitação (BufferBody), mas armazena em buffer todos os arquivos de formulário com várias partes incluídos. - MultipartBodyLengthLimit é o tamanho máximo para arquivos de formulário em buffer (padrão: 128 MB).
-
MemoryBufferThreshold indica o limite de buffer na memória antes da transição para um arquivo de buffer no disco (padrão: 64 KB).
MemoryBufferThresholdAtua como um limite entre arquivos pequenos e grandes, que é aumentado ou reduzido dependendo dos recursos e cenários dos aplicativos.
Para obter mais informações sobre FormOptions, consulte a classe FormOptions na fonte de referência ASP.NET Core.
Note
Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Cenários de carregamento de ficheiros
Duas abordagens gerais para carregar arquivos são buffering e streaming.
Buffering
O ficheiro inteiro é lido em um IFormFile.
IFormFile é uma representação em C# do arquivo usado para processar ou salvar o arquivo.
O disco e a memória usados pelos carregamentos de arquivos dependem do número e do tamanho dos carregamentos simultâneos de arquivos. Se um aplicativo tentar armazenar muitos carregamentos em buffer, o site falhará quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência dos carregamentos de arquivos estiver esgotando os recursos do aplicativo, use o streaming.
Qualquer único arquivo em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
Os arquivos temporários para solicitações maiores são gravados no local nomeado na ASPNETCORE_TEMP variável de ambiente. Se ASPNETCORE_TEMP não estiver definido, os arquivos são gravados na pasta temporária do usuário atual.
O buffer de arquivos pequenos é abordado nas seguintes seções deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e diretamente processado ou salvo pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas de memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming .
Carregue pequenos arquivos com vinculação de modelo em buffer ao armazenamento físico
Para carregar arquivos pequenos, use um formulário com várias partes ou construa uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Pages para carregar um único arquivo (Pages/BufferedSingleFileUploadPhysical.cshtml no aplicativo de exemplo):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file" />
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
O exemplo a seguir é análogo ao exemplo anterior, exceto que:
- JavaScript (Fetch API) é usado para enviar os dados do formulário.
- Não há validação.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Para executar o formulário POST em JavaScript para clientes que não suportam a API de busca, use uma das seguintes abordagens:
Utilize um Polyfill de Fetch (por exemplo, window.fetch polyfill (github/fetch)).
Utilize
XMLHttpRequest. Por exemplo:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
Para suportar carregamentos de ficheiros, os formulários HTML devem especificar um tipo de codificação (enctype) de multipart/form-data.
Para que um files elemento de entrada suporte ao carregamento de vários arquivos, forneça o multiple atributo no <input> elemento :
<input asp-for="FileUpload.FormFiles" type="file" multiple />
Os arquivos individuais carregados para o servidor podem ser acessados através da vinculação de modelo usando IFormFile. O aplicativo de exemplo demonstra vários carregamentos de arquivos em buffer para cenários de banco de dados e armazenamento físico.
Warning
Não use a FileName propriedade de IFormFile outros senão para exibição e registro. Ao exibir ou registrar, o HTML codifica o nome do arquivo. Um ciberatacante pode fornecer um nome de ficheiro malicioso, incluindo caminhos completos ou caminhos relativos. As candidaturas devem:
- Remova o caminho do nome de arquivo fornecido pelo usuário.
- Guarde o nome de ficheiro codificado em HTML com o caminho removido para a interface do utilizador ou o registo em log.
- Gere um novo nome de arquivo aleatório para armazenamento.
O código a seguir remove o caminho do nome do arquivo:
string untrustedFileName = Path.GetFileName(pathName);
Os exemplos fornecidos até agora não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Ao carregar arquivos usando a vinculação de modelo e IFormFile, o método de ação pode aceitar:
- Um único IFormFile.
- Qualquer uma das seguintes coleções que representam vários arquivos:
Note
A associação liga os ficheiros de formulário pelo nome. Por exemplo, o valor HTML name em <input type="file" name="formFile"> deve corresponder ao parâmetro ou propriedade associado de C# (FormFile). Para mais informações, consulte a secção Atribuir o valor do atributo 'nome' ao nome do parâmetro do método POST.
O exemplo a seguir:
- Percorre um ou mais ficheiros carregados.
- Usa Path.GetTempFileName para retornar um caminho completo para um arquivo, incluindo o nome do arquivo.
- Salva os arquivos no sistema de arquivos local usando um nome de arquivo gerado pelo aplicativo.
- Devolve o número total e o tamanho dos ficheiros carregados.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Use Path.GetRandomFileName para gerar um nome de arquivo sem um caminho. No exemplo a seguir, o caminho é obtido da configuração:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
O caminho passado para o FileStreamdeve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, um UnauthorizedAccessException será lançado em tempo de execução.
Os arquivos carregados usando a IFormFile técnica são armazenados em buffer na memória ou no disco no servidor antes do processamento. Dentro do método de ação, o IFormFile conteúdo é acessível como um Stream. Além do sistema de arquivos local, os arquivos podem ser salvos em um compartilhamento de rede ou em um serviço de armazenamento de arquivos, como o armazenamento de Blob do Azure.
Para outro exemplo que itera sobre vários arquivos para carregamento e usa nomes de ficheiro seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs na aplicação de exemplo.
Warning
No .NET 7 ou versões anteriores, Path.GetTempFileName lança um IOException quando mais de 65.535 arquivos são criados sem excluir arquivos temporários anteriores. O limite de 65.535 arquivos é um limite por servidor. Para obter mais informações sobre esse limite no sistema operacional Windows, consulte os comentários nos seguintes artigos:
Carregue arquivos pequenos com vinculação de modelo em buffer a um banco de dados
Para armazenar dados de arquivo binário em um banco de dados usando o Entity Framework, defina uma Byte propriedade de matriz na entidade:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Especifique uma propriedade de modelo de página para a classe que inclui um IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo acoplado. O exemplo anterior usa uma propriedade de modelo vinculada.
O FileUpload é usado no formulário de Páginas Razor.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Quando o formulário for enviado para o servidor, copie IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext armazena o contexto do banco de dados do aplicativo:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
O exemplo anterior é semelhante a um cenário demonstrado no aplicativo de exemplo:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não dependa ou confie na FileName propriedade de IFormFile sem validação. A FileName propriedade só deve ser usada para fins de exibição e somente após a codificação HTML.
Os exemplos fornecidos não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Carregue arquivos grandes com streaming
Para cenários em que são necessários carregamentos de arquivos grandes, os uploads de streaming permitem que você processe dados de formulários de várias partes recebidos diretamente, sem armazenar em buffer todo o arquivo na memória ou no disco por meio da vinculação de modelo. Essa técnica é especialmente importante para arquivos que podem exceder os limites de buffer do servidor ou da estrutura.
O aplicativo de exemplo para 9.x demonstra como um servidor pode receber um arquivo e transmitir os dados diretamente para o disco, suportando cancelamento robusto por meio do token de cancelamento da solicitação HTTP.
MultipartReader é um utilitário ASP.NET Core para ler arquivos de solicitações recebidas. O trecho a seguir mostra como processar uma solicitação e transmitir o arquivo para um outputStream (como um FileStream):
// Read the boundary from the Content-Type header
var boundary = HeaderUtilities.RemoveQuotes(
MediaTypeHeaderValue.Parse(request.ContentType).Boundary).Value;
// Use MultipartReader to stream data to a destination
var reader = new MultipartReader(boundary, Request.Body);
MultipartSection? section;
while ((section = await reader.ReadNextSectionAsync(cancellationToken)) != null)
{
var contentDisposition = section.GetContentDispositionHeader();
if (contentDisposition != null && contentDisposition.IsFileDisposition())
{
await section.Body.CopyToAsync(outputStream, cancellationToken);
}
}
IFormFeature é um wrapper em torno de MultipartReader que não requer que escreva código manual de parsing do corpo da solicitação. Você pode usar seu ReadFormAsync método para preencher os dados de formulário da solicitação e, em seguida, acessar os arquivos carregados da coleção interna:
// Get the IFormFeature and read the form
var formFeature = Request.HttpContext.Features.GetRequiredFeature<IFormFeature>();
await formFeature.ReadFormAsync(cancellationToken);
// Access the uploaded file (example: first file)
var filePath = Request.Form.Files.First().FileName;
return Results.Ok($"Saved file at {filePath}");
Para cenários avançados, analise manualmente o corpo da solicitação bruta usando HttpRequest.BodyReader, que expõe um IPipeReader streaming de baixo nível e alto desempenho. A aplicação de exemplo inclui handlers de endpoint que utilizam IPipeReader tanto na API mínima quanto nos controladores.
O exemplo 3.1 demonstra como usar JavaScript para transmitir um arquivo para uma ação do controlador. O token antifalsificação do arquivo é gerado usando um atributo de filtro personalizado e passado para os cabeçalhos HTTP do cliente em vez de no corpo da solicitação. Como o método action processa os dados carregados diretamente, a vinculação do modelo de formulário é desabilitada por outro filtro personalizado. No interior da ação, o conteúdo do formulário é lido usando um MultipartReader, que lê cada elemento MultipartSection, processando o arquivo ou armazenando o conteúdo conforme apropriado. Após a leitura das secções multipartes, a ação executa as suas próprias ligações de modelo.
A resposta da página inicial carrega o formulário e salva um token antifalsificação em um cookie (através do atributo GenerateAntiforgeryTokenCookieAttribute). O atributo usa o suporte antifalsificação interno do ASP.NET Core para definir um cookie com um token de solicitação:
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
O DisableFormValueModelBindingAttribute é usado para desativar a vinculação de modelo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
No aplicativo de exemplo, GenerateAntiforgeryTokenCookieAttribute e DisableFormValueModelBindingAttribute são aplicados como filtros aos modelos de aplicação de página de /StreamedSingleFileUploadDb e /StreamedSingleFileUploadPhysical em Startup.ConfigureServices usando as convenções de Razor Pages.
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Como a vinculação de modelo não lê o formulário, os parâmetros que deveriam ser vinculados a partir do formulário não se vinculam (consulta, rota e cabeçalho continuam a funcionar). O método de ação funciona diretamente com a propriedade Request. Um MultipartReader é utilizado para ler cada seção. Os dados de chave/valor são armazenados em arquivo KeyValueAccumulator. Depois que as seções multipartes são lidas, o conteúdo do KeyValueAccumulator é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase para transmissão para uma base de dados com EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
O método completo StreamingController.UploadPhysical para transmitir para um local físico:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
No aplicativo de exemplo, as verificações de validação são tratadas pelo FileHelpers.ProcessStreamedFile.
Validation
A classe do aplicativo de exemplo demonstra várias verificações para uploads FileHelpers de arquivos em buffer IFormFile e transmitidos. Para processar IFormFile carregamentos de ficheiros em buffer na aplicação de exemplo, consulte o método ProcessFormFile no ficheiro Utilities/FileHelpers.cs. Para processar arquivos transmitidos, consulte o ProcessStreamedFile método no mesmo arquivo.
Warning
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não verificam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de verificação de vírus/malware é usada no arquivo antes de disponibilizá-lo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo prático de técnicas de validação, não implemente a classe FileHelpers num aplicativo de produção, a menos que:
- Compreenda completamente a implementação.
- Modifique a implementação conforme apropriado para o ambiente e as especificações do aplicativo.
Nunca implemente indiscriminadamente código de segurança em um aplicativo sem atender a esses requisitos.
Validação de conteúdo
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos é exigente em recursos de servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for diminuído devido à verificação de arquivos, considere descarregar o trabalho de verificação para um serviço em segundo plano, possivelmente um serviço em execução em um servidor diferente do servidor do aplicativo. Normalmente, os ficheiros carregados são mantidos numa área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo passa, ele é movido para o local normal de armazenamento de arquivos. Essas etapas geralmente são executadas em conjunto com um registro de banco de dados que indica o status de varredura de um arquivo. Ao usar essa abordagem, o aplicativo e o servidor do aplicativo permanecem focados em responder às solicitações.
Validação da extensão de arquivo
A extensão do ficheiro carregado deve ser verificada em relação a uma lista de extensões permitidas. Por exemplo:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Validação de assinatura de arquivo
A assinatura de um arquivo é determinada pelos primeiros bytes no início de um arquivo. Esses bytes podem ser usados para indicar se a extensão corresponde ao conteúdo do arquivo. O aplicativo de exemplo verifica assinaturas de arquivo para alguns tipos de arquivo comuns. No exemplo a seguir, a assinatura de arquivo para uma imagem JPEG é verificada em relação ao arquivo:
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Para obter assinaturas de arquivo adicionais, use um banco de dados de assinaturas de arquivo (resultado da pesquisa do Google) e especificações oficiais do arquivo. A consulta das especificações oficiais dos ficheiros pode garantir que as assinaturas selecionadas são válidas.
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 de Razor, utilize sempre o conteúdo do nome do ficheiro proveniente de uma solicitação de um utilizador.
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.
Validação de tamanho
Limite o tamanho dos ficheiros carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido via Configuração do ficheiro:
{
"FileSizeLimit": 2097152
}
O FileSizeLimit é injetado em PageModel classes:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Quando um tamanho de arquivo excede o limite, o arquivo é rejeitado:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Corresponder o valor do atributo name ao nome do parâmetro do método POST
Em formulários queRazor não enviam dados por POST ou usam JavaScript FormData diretamente para enviar dados, o nome especificado no elemento do formulário ou FormData deve corresponder ao nome do parâmetro na ação do controlador.
No exemplo a seguir:
Ao usar um
<input>elemento , onameatributo é definido como o valorbattlePlans:<input type="file" name="battlePlans" multiple>Ao usar
FormDataem JavaScript, o nome é definido com o valorbattlePlans:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Use um nome correspondente para o parâmetro do método C# (battlePlans):
Para um Razor método de manipulador de página Pages chamado
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)Para um método de ação do controlador MVC POST:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Configuração do servidor e do aplicativo
Limite de comprimento do corpo com várias partes
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo com várias partes. As seções de formulário que excedem esse limite lançam um InvalidDataException quando analisadas. O padrão é 134.217.728 (128 MB). Personalize o limite usando a MultipartBodyLengthLimit configuração em Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute é usado para definir o MultipartBodyLengthLimit para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Em um aplicativo Pages ou MVC Razor , aplique o filtro ao modelo de página ou método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel Tamanho máximo do corpo da solicitação
Para aplicativos hospedados pelo Kestrel, o tamanho máximo padrão do corpo da solicitação é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite usando a opção de servidor MaxRequestBodySizeKestrel :
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute é usado para definir o MaxRequestBodySize para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Em um Razor aplicativo de páginas ou um aplicativo MVC, aplique o filtro à classe ou ao método de ação do manipulador de página:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
O RequestSizeLimitAttribute também pode ser aplicado usando a @attributeRazor diretiva:
@attribute [RequestSizeLimitAttribute(52428800)]
Outros Kestrel limites
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
IIS
O limite de solicitação padrão (maxAllowedContentLength) é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite no ficheiro web.config. No exemplo a seguir, o limite é definido como 50 MB (52.428.800 bytes):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength configuração só se aplica ao IIS. Para obter mais informações, consulte Limites de <requestLimits>solicitação .
Troubleshoot
Abaixo estão alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
Erro Não encontrado quando implantado em um servidor IIS
O erro a seguir indica que o arquivo carregado excede o comprimento de conteúdo configurado do servidor:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Para obter mais informações, consulte a seção do IIS.
Falha de ligação
Um erro de conexão e a redefinição da conexão do servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação de Kestrel. Para obter mais informações, consulte a seção Tamanho máximo do corpo daKestrel solicitação. Kestrel Os limites de conexão do cliente também podem exigir ajustes.
Exceção de referência nula com IFormFile
Se o controlador estiver a aceitar ficheiros carregados com IFormFile, mas o valor estiver como null, confirme se o formulário HTML está a especificar um valor enctype de multipart/form-data. Se esse atributo não estiver definido no elemento , o upload do arquivo não ocorrerá e todos os <form> argumentos associados IFormFile serão null. Confirme também se o nome de carregamento nos dados do formulário corresponde ao nome da aplicação.
O fluxo foi demasiado longo
Os exemplos neste tópico contam com MemoryStream para armazenar o conteúdo do ficheiro carregado. O limite de tamanho de a MemoryStream é int.MaxValue. Se o cenário de carregamento de arquivos do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de um único MemoryStream para armazenar o conteúdo de um arquivo carregado.
O ASP.NET Core suporta o upload de um ou mais arquivos usando a vinculação de modelo em buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Visualizar ou descarregar amostra de código (como descarregar)
Considerações de segurança
Tenha cuidado ao fornecer aos usuários a capacidade de carregar arquivos para um servidor. Os ciberatacantes podem tentar:
- Execute ataques de negação de serviço .
- Carregue vírus ou malware.
- Comprometa redes e servidores de outras formas.
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
- Carregue ficheiros para uma área de carregamento de ficheiros dedicada, de preferência para uma unidade que não seja do sistema. Uma localização dedicada facilita a imposição de restrições de segurança aos ficheiros carregados. Desative as permissões de execução no local de carregamento do arquivo.†
- Não persista os arquivos carregados na mesma árvore de diretórios do aplicativo.†
- Use um nome de arquivo seguro determinado pelo aplicativo. Não use um nome de arquivo fornecido pelo usuário ou o nome de arquivo não confiável do arquivo carregado.† HTML codificar o nome de arquivo não confiável ao exibi-lo. Por exemplo, registrar o nome do arquivo ou exibir na interface do usuário (Razor automaticamente HTML codifica a saída).
- Permita apenas extensões de arquivo aprovadas para a especificação de design do aplicativo.†
- Verifique se as verificações do lado do cliente são executadas no servidor.† As verificações do lado do cliente são fáceis de contornar.
- Verifique o tamanho de um ficheiro carregado. Defina um limite máximo de tamanho para evitar carregamentos grandes.†
- Quando os arquivos não devem ser substituídos por um arquivo carregado com o mesmo nome, verifique o nome do arquivo em relação ao banco de dados ou ao armazenamento físico antes de carregá-lo.
- Execute um verificador de vírus/malware no conteúdo carregado antes que o arquivo seja armazenado.
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Warning
Carregar código malicioso para um sistema é frequentemente o primeiro passo para executar código que pode:
- Ganhe completamente o controle de um sistema.
- Sobrecarregue um sistema com o resultado de que o sistema falha.
- Comprometa os dados do usuário ou do sistema.
- Aplique graffiti a uma interface do usuário pública.
Para obter informações sobre como reduzir vulnerabilidades ao aceitar arquivos de usuários, consulte os seguintes recursos:
Para obter mais informações sobre como implementar medidas de segurança, incluindo exemplos do aplicativo de exemplo, consulte a seção Validação .
Cenários de armazenamento
As opções comuns de armazenamento para arquivos incluem:
Database
- Para carregamentos de arquivos pequenos, um banco de dados geralmente é mais rápido do que as opções de armazenamento físico (sistema de arquivos ou compartilhamento de rede).
- Um banco de dados geralmente é mais conveniente do que as opções de armazenamento físico porque a recuperação de um registro de banco de dados para dados do usuário pode fornecer simultaneamente o conteúdo do arquivo (por exemplo, uma imagem de avatar).
- Um banco de dados é potencialmente mais barato do que usar um serviço de armazenamento de dados.
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
- Para carregamentos de ficheiros grandes:
- Os limites do banco de dados podem restringir o tamanho do upload.
- O armazenamento físico geralmente é menos econômico do que o armazenamento em um banco de dados.
- O armazenamento físico é potencialmente mais barato do que o uso de um serviço de armazenamento de dados.
- O processo do aplicativo deve ter permissões de leitura e gravação no local de armazenamento. Nunca conceda permissão de execução.
- Para carregamentos de ficheiros grandes:
Serviço de armazenamento de dados (por exemplo, Armazenamento de Blobs do Azure)
- Os serviços geralmente oferecem escalabilidade e resiliência aprimoradas em relação a soluções locais que geralmente estão sujeitas a pontos únicos de falha.
- Os serviços são potencialmente mais baratos em cenários de grandes infraestruturas de armazenamento.
Para obter mais informações, consulte Guia de início rápido: usar o .NET para criar um blob no armazenamento de objetos.
Cenários de carregamento de ficheiros
Duas abordagens gerais para carregar arquivos são buffering e streaming.
Buffering
O arquivo inteiro é lido em um IFormFile, que é uma representação em C# do arquivo usado para processar ou salvar o arquivo.
Os recursos (disco, memória) utilizados pelos carregamentos de ficheiros dependem do número e do tamanho dos carregamentos simultâneos de ficheiros. Se um aplicativo tentar armazenar muitos carregamentos em buffer, o site falhará quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência dos carregamentos de arquivos estiver esgotando os recursos do aplicativo, use o streaming.
Note
Qualquer único arquivo em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
O buffer de arquivos pequenos é abordado nas seguintes seções deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e diretamente processado ou salvo pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas de memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming .
Carregue pequenos arquivos com vinculação de modelo em buffer ao armazenamento físico
Para carregar arquivos pequenos, use um formulário com várias partes ou construa uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Pages para carregar um único arquivo (Pages/BufferedSingleFileUploadPhysical.cshtml no aplicativo de exemplo):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
O exemplo a seguir é análogo ao exemplo anterior, exceto que:
- JavaScript (Fetch API) é usado para enviar os dados do formulário.
- Não há validação.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Para executar o formulário POST em JavaScript para clientes que não suportam a API de busca, use uma das seguintes abordagens:
Utilize um Polyfill de Fetch (por exemplo, window.fetch polyfill (github/fetch)).
Utilize
XMLHttpRequest. Por exemplo:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
Para suportar carregamentos de ficheiros, os formulários HTML devem especificar um tipo de codificação (enctype) de multipart/form-data.
Para que um files elemento de entrada suporte ao carregamento de vários arquivos, forneça o multiple atributo no <input> elemento :
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Os arquivos individuais carregados para o servidor podem ser acessados através da vinculação de modelo usando IFormFile. O aplicativo de exemplo demonstra vários carregamentos de arquivos em buffer para cenários de banco de dados e armazenamento físico.
Warning
Não use a FileName propriedade de IFormFile outros senão para exibição e registro. Ao exibir ou registrar, o HTML codifica o nome do arquivo. Um ciberatacante pode fornecer um nome de ficheiro malicioso, incluindo caminhos completos ou caminhos relativos. As candidaturas devem:
- Remova o caminho do nome de arquivo fornecido pelo usuário.
- Guarde o nome de ficheiro codificado em HTML com o caminho removido para a interface do utilizador ou o registo em log.
- Gere um novo nome de arquivo aleatório para armazenamento.
O código a seguir remove o caminho do nome do arquivo:
string untrustedFileName = Path.GetFileName(pathName);
Os exemplos fornecidos até agora não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Ao carregar arquivos usando a vinculação de modelo e IFormFile, o método de ação pode aceitar:
- Um único IFormFile.
- Qualquer uma das seguintes coleções que representam vários arquivos:
Note
A associação liga os ficheiros de formulário pelo nome. Por exemplo, o valor HTML name em <input type="file" name="formFile"> deve corresponder ao parâmetro ou propriedade associado de C# (FormFile). Para mais informações, consulte a secção Atribuir o valor do atributo 'nome' ao nome do parâmetro do método POST.
O exemplo a seguir:
- Percorre um ou mais ficheiros carregados.
- Usa Path.GetTempFileName para retornar um caminho completo para um arquivo, incluindo o nome do arquivo.
- Salva os arquivos no sistema de arquivos local usando um nome de arquivo gerado pelo aplicativo.
- Devolve o número total e o tamanho dos ficheiros carregados.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Use Path.GetRandomFileName para gerar um nome de arquivo sem um caminho. No exemplo a seguir, o caminho é obtido da configuração:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
O caminho passado para o FileStreamdeve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, um UnauthorizedAccessException será lançado em tempo de execução.
Os arquivos carregados usando a IFormFile técnica são armazenados em buffer na memória ou no disco no servidor antes do processamento. Dentro do método de ação, o IFormFile conteúdo é acessível como um Stream. Além do sistema de arquivos local, os arquivos podem ser salvos em um compartilhamento de rede ou em um serviço de armazenamento de arquivos, como o armazenamento de Blob do Azure.
Para outro exemplo que itera sobre vários arquivos para carregamento e usa nomes de ficheiro seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs na aplicação de exemplo.
Warning
Path.GetTempFileName lança um IOException se mais de 65.535 arquivos são criados sem excluir arquivos temporários anteriores. O limite de 65.535 arquivos é um limite por servidor. Para obter mais informações sobre esse limite no sistema operacional Windows, consulte os comentários nos seguintes tópicos:
Carregue arquivos pequenos com vinculação de modelo em buffer a um banco de dados
Para armazenar dados de arquivo binário em um banco de dados usando o Entity Framework, defina uma Byte propriedade de matriz na entidade:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Especifique uma propriedade de modelo de página para a classe que inclui um IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo acoplado. O exemplo anterior usa uma propriedade de modelo vinculada.
O FileUpload é usado no formulário de Páginas Razor.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Quando o formulário for enviado para o servidor, copie IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext armazena o contexto do banco de dados do aplicativo:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
O exemplo anterior é semelhante a um cenário demonstrado no aplicativo de exemplo:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não dependa ou confie na FileName propriedade de IFormFile sem validação. A FileName propriedade só deve ser usada para fins de exibição e somente após a codificação HTML.
Os exemplos fornecidos não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Carregue arquivos grandes com streaming
O exemplo a seguir demonstra como usar JavaScript para transmitir um arquivo para uma ação do controlador. O token antifalsificação do arquivo é gerado usando um atributo de filtro personalizado e passado para os cabeçalhos HTTP do cliente em vez de no corpo da solicitação. Como o método action processa os dados carregados diretamente, a vinculação do modelo de formulário é desabilitada por outro filtro personalizado. No interior da ação, o conteúdo do formulário é lido usando um MultipartReader, que lê cada elemento MultipartSection, processando o arquivo ou armazenando o conteúdo conforme apropriado. Após a leitura das secções multipartes, a ação executa as suas próprias ligações de modelo.
A resposta da página inicial carrega o formulário e salva um token antifalsificação em um cookie (através do atributo GenerateAntiforgeryTokenCookieAttribute). O atributo usa o suporte antifalsificação interno do ASP.NET Core para definir um cookie com um token de solicitação:
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
O DisableFormValueModelBindingAttribute é usado para desativar a vinculação de modelo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
No aplicativo de exemplo, GenerateAntiforgeryTokenCookieAttribute e DisableFormValueModelBindingAttribute são aplicados como filtros aos modelos de aplicação de página de /StreamedSingleFileUploadDb e /StreamedSingleFileUploadPhysical em Startup.ConfigureServices usando as convenções de Razor Pages.
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Como a vinculação de modelo não lê o formulário, os parâmetros que deveriam ser vinculados a partir do formulário não se vinculam (consulta, rota e cabeçalho continuam a funcionar). O método de ação funciona diretamente com a propriedade Request. Um MultipartReader é utilizado para ler cada seção. Os dados de chave/valor são armazenados em arquivo KeyValueAccumulator. Depois que as seções multipartes são lidas, o conteúdo do KeyValueAccumulator é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase para transmissão para uma base de dados com EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
O método completo StreamingController.UploadPhysical para transmitir para um local físico:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
No aplicativo de exemplo, as verificações de validação são tratadas pelo FileHelpers.ProcessStreamedFile.
Validation
A classe do aplicativo de exemplo demonstra várias verificações para uploads FileHelpers de arquivos em buffer IFormFile e transmitidos. Para processar IFormFile carregamentos de ficheiros em buffer na aplicação de exemplo, consulte o método ProcessFormFile no ficheiro Utilities/FileHelpers.cs. Para processar arquivos transmitidos, consulte o ProcessStreamedFile método no mesmo arquivo.
Warning
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não verificam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de verificação de vírus/malware é usada no arquivo antes de disponibilizá-lo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo prático de técnicas de validação, não implemente a classe FileHelpers num aplicativo de produção, a menos que:
- Compreenda completamente a implementação.
- Modifique a implementação conforme apropriado para o ambiente e as especificações do aplicativo.
Nunca implemente indiscriminadamente código de segurança em um aplicativo sem atender a esses requisitos.
Validação de conteúdo
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos é exigente em recursos de servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for diminuído devido à verificação de arquivos, considere descarregar o trabalho de verificação para um serviço em segundo plano, possivelmente um serviço em execução em um servidor diferente do servidor do aplicativo. Normalmente, os ficheiros carregados são mantidos numa área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo passa, ele é movido para o local normal de armazenamento de arquivos. Essas etapas geralmente são executadas em conjunto com um registro de banco de dados que indica o status de varredura de um arquivo. Ao usar essa abordagem, o aplicativo e o servidor do aplicativo permanecem focados em responder às solicitações.
Validação da extensão de arquivo
A extensão do ficheiro carregado deve ser verificada em relação a uma lista de extensões permitidas. Por exemplo:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Validação de assinatura de arquivo
A assinatura de um arquivo é determinada pelos primeiros bytes no início de um arquivo. Esses bytes podem ser usados para indicar se a extensão corresponde ao conteúdo do arquivo. O aplicativo de exemplo verifica assinaturas de arquivo para alguns tipos de arquivo comuns. No exemplo a seguir, a assinatura de arquivo para uma imagem JPEG é verificada em relação ao arquivo:
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Para obter assinaturas de arquivo adicionais, use um banco de dados de assinaturas de arquivo (resultado da pesquisa do Google) e especificações oficiais do arquivo. A consulta das especificações oficiais dos ficheiros pode garantir que as assinaturas selecionadas são válidas.
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 de Razor, utilize sempre o conteúdo do nome do ficheiro proveniente de uma solicitação de um utilizador.
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.
Validação de tamanho
Limite o tamanho dos ficheiros carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido via Configuração do ficheiro:
{
"FileSizeLimit": 2097152
}
O FileSizeLimit é injetado em PageModel classes:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Quando um tamanho de arquivo excede o limite, o arquivo é rejeitado:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Corresponder o valor do atributo name ao nome do parâmetro do método POST
Em formulários queRazor não enviam dados por POST ou usam JavaScript FormData diretamente para enviar dados, o nome especificado no elemento do formulário ou FormData deve corresponder ao nome do parâmetro na ação do controlador.
No exemplo a seguir:
Ao usar um
<input>elemento , onameatributo é definido como o valorbattlePlans:<input type="file" name="battlePlans" multiple>Ao usar
FormDataem JavaScript, o nome é definido com o valorbattlePlans:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Use um nome correspondente para o parâmetro do método C# (battlePlans):
Para um Razor método de manipulador de página Pages chamado
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)Para um método de ação do controlador MVC POST:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Configuração do servidor e do aplicativo
Limite de comprimento do corpo com várias partes
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo com várias partes. As seções de formulário que excedem esse limite lançam um InvalidDataException quando analisadas. O padrão é 134.217.728 (128 MB). Personalize o limite usando a MultipartBodyLengthLimit configuração em Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute é usado para definir o MultipartBodyLengthLimit para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Em um aplicativo Pages ou MVC Razor , aplique o filtro ao modelo de página ou método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel Tamanho máximo do corpo da solicitação
Para aplicativos hospedados pelo Kestrel, o tamanho máximo padrão do corpo da solicitação é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite usando a opção de servidor MaxRequestBodySizeKestrel :
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute é usado para definir o MaxRequestBodySize para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Em um Razor aplicativo de páginas ou um aplicativo MVC, aplique o filtro à classe ou ao método de ação do manipulador de página:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
O RequestSizeLimitAttribute também pode ser aplicado usando a @attributeRazor diretiva:
@attribute [RequestSizeLimitAttribute(52428800)]
Outros Kestrel limites
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
IIS
O limite de solicitação padrão (maxAllowedContentLength) é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite no ficheiro web.config. No exemplo a seguir, o limite é definido como 50 MB (52.428.800 bytes):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength configuração só se aplica ao IIS. Para obter mais informações, consulte Limites de <requestLimits>solicitação .
Aumente o tamanho máximo do corpo da solicitação para a solicitação HTTP definindo IISServerOptions.MaxRequestBodySize em Startup.ConfigureServices. No exemplo a seguir, o limite é definido como 50 MB (52.428.800 bytes):
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
Para obter mais informações, consulte Host ASP.NET Core no Windows com IIS.
Troubleshoot
Abaixo estão alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
Erro Não encontrado quando implantado em um servidor IIS
O erro a seguir indica que o arquivo carregado excede o comprimento de conteúdo configurado do servidor:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Para obter mais informações, consulte a seção do IIS.
Falha de ligação
Um erro de conexão e a redefinição da conexão do servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação de Kestrel. Para obter mais informações, consulte a seção Tamanho máximo do corpo daKestrel solicitação. Kestrel Os limites de conexão do cliente também podem exigir ajustes.
Exceção de referência nula com IFormFile
Se o controlador estiver a aceitar ficheiros carregados com IFormFile, mas o valor estiver como null, confirme se o formulário HTML está a especificar um valor enctype de multipart/form-data. Se esse atributo não estiver definido no elemento , o upload do arquivo não ocorrerá e todos os <form> argumentos associados IFormFile serão null. Confirme também se o nome de carregamento nos dados do formulário corresponde ao nome da aplicação.
O fluxo foi demasiado longo
Os exemplos neste tópico contam com MemoryStream para armazenar o conteúdo do ficheiro carregado. O limite de tamanho de a MemoryStream é int.MaxValue. Se o cenário de carregamento de arquivos do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de um único MemoryStream para armazenar o conteúdo de um arquivo carregado.
O ASP.NET Core suporta o upload de um ou mais arquivos usando a vinculação de modelo em buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Visualizar ou descarregar amostra de código (como descarregar)
Considerações de segurança
Tenha cuidado ao fornecer aos usuários a capacidade de carregar arquivos para um servidor. Os ciberatacantes podem tentar:
- Execute ataques de negação de serviço .
- Carregue vírus ou malware.
- Comprometa redes e servidores de outras formas.
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
- Carregue ficheiros para uma área de carregamento de ficheiros dedicada, de preferência para uma unidade que não seja do sistema. Uma localização dedicada facilita a imposição de restrições de segurança aos ficheiros carregados. Desative as permissões de execução no local de carregamento do arquivo.†
- Não persista os arquivos carregados na mesma árvore de diretórios do aplicativo.†
- Use um nome de arquivo seguro determinado pelo aplicativo. Não use um nome de arquivo fornecido pelo usuário ou o nome de arquivo não confiável do arquivo carregado.† HTML codificar o nome de arquivo não confiável ao exibi-lo. Por exemplo, registrar o nome do arquivo ou exibir na interface do usuário (Razor automaticamente HTML codifica a saída).
- Permita apenas extensões de arquivo aprovadas para a especificação de design do aplicativo.†
- Verifique se as verificações do lado do cliente são executadas no servidor.† As verificações do lado do cliente são fáceis de contornar.
- Verifique o tamanho de um ficheiro carregado. Defina um limite máximo de tamanho para evitar carregamentos grandes.†
- Quando os arquivos não devem ser substituídos por um arquivo carregado com o mesmo nome, verifique o nome do arquivo em relação ao banco de dados ou ao armazenamento físico antes de carregá-lo.
- Execute um verificador de vírus/malware no conteúdo carregado antes que o arquivo seja armazenado.
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Warning
Carregar código malicioso para um sistema é frequentemente o primeiro passo para executar código que pode:
- Ganhe completamente o controle de um sistema.
- Sobrecarregue um sistema com o resultado de que o sistema falha.
- Comprometa os dados do usuário ou do sistema.
- Aplique graffiti a uma interface do usuário pública.
Para obter informações sobre como reduzir vulnerabilidades ao aceitar arquivos de usuários, consulte os seguintes recursos:
Para obter mais informações sobre como implementar medidas de segurança, incluindo exemplos do aplicativo de exemplo, consulte a seção Validação .
Cenários de armazenamento
As opções comuns de armazenamento para arquivos incluem:
Database
- Para carregamentos de arquivos pequenos, um banco de dados geralmente é mais rápido do que as opções de armazenamento físico (sistema de arquivos ou compartilhamento de rede).
- Um banco de dados geralmente é mais conveniente do que as opções de armazenamento físico porque a recuperação de um registro de banco de dados para dados do usuário pode fornecer simultaneamente o conteúdo do arquivo (por exemplo, uma imagem de avatar).
- Um banco de dados é potencialmente mais barato do que usar um serviço de armazenamento de dados.
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
- Para carregamentos de ficheiros grandes:
- Os limites do banco de dados podem restringir o tamanho do upload.
- O armazenamento físico geralmente é menos econômico do que o armazenamento em um banco de dados.
- O armazenamento físico é potencialmente mais barato do que o uso de um serviço de armazenamento de dados.
- O processo do aplicativo deve ter permissões de leitura e gravação no local de armazenamento. Nunca conceda permissão de execução.
- Para carregamentos de ficheiros grandes:
Serviço de armazenamento de dados (por exemplo, Armazenamento de Blobs do Azure)
- Os serviços geralmente oferecem escalabilidade e resiliência aprimoradas em relação a soluções locais que geralmente estão sujeitas a pontos únicos de falha.
- Os serviços são potencialmente mais baratos em cenários de grandes infraestruturas de armazenamento.
Para obter mais informações, consulte Guia de início rápido: usar o .NET para criar um blob no armazenamento de objetos. O tópico demonstra
UploadFromFileAsync, masUploadFromStreamAsyncpode ser usado para salvar um FileStream para armazenamento de blob ao trabalhar com um Stream.
Cenários de carregamento de ficheiros
Duas abordagens gerais para carregar arquivos são buffering e streaming.
Buffering
O arquivo inteiro é lido em um IFormFile, que é uma representação em C# do arquivo usado para processar ou salvar o arquivo.
Os recursos (disco, memória) utilizados pelos carregamentos de ficheiros dependem do número e do tamanho dos carregamentos simultâneos de ficheiros. Se um aplicativo tentar armazenar muitos carregamentos em buffer, o site falhará quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência dos carregamentos de arquivos estiver esgotando os recursos do aplicativo, use o streaming.
Note
Qualquer único arquivo em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
O buffer de arquivos pequenos é abordado nas seguintes seções deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e diretamente processado ou salvo pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas de memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming .
Carregue pequenos arquivos com vinculação de modelo em buffer ao armazenamento físico
Para carregar arquivos pequenos, use um formulário com várias partes ou construa uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Pages para carregar um único arquivo (Pages/BufferedSingleFileUploadPhysical.cshtml no aplicativo de exemplo):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
O exemplo a seguir é análogo ao exemplo anterior, exceto que:
- JavaScript (Fetch API) é usado para enviar os dados do formulário.
- Não há validação.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Para executar o formulário POST em JavaScript para clientes que não suportam a API de busca, use uma das seguintes abordagens:
Utilize um Polyfill de Fetch (por exemplo, window.fetch polyfill (github/fetch)).
Utilize
XMLHttpRequest. Por exemplo:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
Para suportar carregamentos de ficheiros, os formulários HTML devem especificar um tipo de codificação (enctype) de multipart/form-data.
Para que um files elemento de entrada suporte ao carregamento de vários arquivos, forneça o multiple atributo no <input> elemento :
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Os arquivos individuais carregados para o servidor podem ser acessados através da vinculação de modelo usando IFormFile. O aplicativo de exemplo demonstra vários carregamentos de arquivos em buffer para cenários de banco de dados e armazenamento físico.
Warning
Não use a FileName propriedade de IFormFile outros senão para exibição e registro. Ao exibir ou registrar, o HTML codifica o nome do arquivo. Um ciberatacante pode fornecer um nome de ficheiro malicioso, incluindo caminhos completos ou caminhos relativos. As candidaturas devem:
- Remova o caminho do nome de arquivo fornecido pelo usuário.
- Guarde o nome de ficheiro codificado em HTML com o caminho removido para a interface do utilizador ou o registo em log.
- Gere um novo nome de arquivo aleatório para armazenamento.
O código a seguir remove o caminho do nome do arquivo:
string untrustedFileName = Path.GetFileName(pathName);
Os exemplos fornecidos até agora não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Ao carregar arquivos usando a vinculação de modelo e IFormFile, o método de ação pode aceitar:
- Um único IFormFile.
- Qualquer uma das seguintes coleções que representam vários arquivos:
Note
A associação liga os ficheiros de formulário pelo nome. Por exemplo, o valor HTML name em <input type="file" name="formFile"> deve corresponder ao parâmetro ou propriedade associado de C# (FormFile). Para mais informações, consulte a secção Atribuir o valor do atributo 'nome' ao nome do parâmetro do método POST.
O exemplo a seguir:
- Percorre um ou mais ficheiros carregados.
- Usa Path.GetTempFileName para retornar um caminho completo para um arquivo, incluindo o nome do arquivo.
- Salva os arquivos no sistema de arquivos local usando um nome de arquivo gerado pelo aplicativo.
- Devolve o número total e o tamanho dos ficheiros carregados.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Use Path.GetRandomFileName para gerar um nome de arquivo sem um caminho. No exemplo a seguir, o caminho é obtido da configuração:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
O caminho passado para o FileStreamdeve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, um UnauthorizedAccessException será lançado em tempo de execução.
Os arquivos carregados usando a IFormFile técnica são armazenados em buffer na memória ou no disco no servidor antes do processamento. Dentro do método de ação, o IFormFile conteúdo é acessível como um Stream. Além do sistema de arquivos local, os arquivos podem ser salvos em um compartilhamento de rede ou em um serviço de armazenamento de arquivos, como o armazenamento de Blob do Azure.
Para outro exemplo que itera sobre vários arquivos para carregamento e usa nomes de ficheiro seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs na aplicação de exemplo.
Warning
Path.GetTempFileName lança um IOException se mais de 65.535 arquivos são criados sem excluir arquivos temporários anteriores. O limite de 65.535 arquivos é um limite por servidor. Para obter mais informações sobre esse limite no sistema operacional Windows, consulte os comentários nos seguintes tópicos:
Carregue arquivos pequenos com vinculação de modelo em buffer a um banco de dados
Para armazenar dados de arquivo binário em um banco de dados usando o Entity Framework, defina uma Byte propriedade de matriz na entidade:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Especifique uma propriedade de modelo de página para a classe que inclui um IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo acoplado. O exemplo anterior usa uma propriedade de modelo vinculada.
O FileUpload é usado no formulário de Páginas Razor.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Quando o formulário for enviado para o servidor, copie IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext armazena o contexto do banco de dados do aplicativo:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
O exemplo anterior é semelhante a um cenário demonstrado no aplicativo de exemplo:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não dependa ou confie na FileName propriedade de IFormFile sem validação. A FileName propriedade só deve ser usada para fins de exibição e somente após a codificação HTML.
Os exemplos fornecidos não levam em conta considerações de segurança. Informações adicionais são fornecidas pelas seguintes seções e pelo aplicativo de exemplo:
Carregue arquivos grandes com streaming
O exemplo a seguir demonstra como usar JavaScript para transmitir um arquivo para uma ação do controlador. O token antifalsificação do arquivo é gerado usando um atributo de filtro personalizado e passado para os cabeçalhos HTTP do cliente em vez de no corpo da solicitação. Como o método action processa os dados carregados diretamente, a vinculação do modelo de formulário é desabilitada por outro filtro personalizado. No interior da ação, o conteúdo do formulário é lido usando um MultipartReader, que lê cada elemento MultipartSection, processando o arquivo ou armazenando o conteúdo conforme apropriado. Após a leitura das secções multipartes, a ação executa as suas próprias ligações de modelo.
A resposta da página inicial carrega o formulário e salva um token antifalsificação em um cookie (através do atributo GenerateAntiforgeryTokenCookieAttribute). O atributo usa o suporte antifalsificação interno do ASP.NET Core para definir um cookie com um token de solicitação:
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
O DisableFormValueModelBindingAttribute é usado para desativar a vinculação de modelo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
No aplicativo de exemplo, GenerateAntiforgeryTokenCookieAttribute e DisableFormValueModelBindingAttribute são aplicados como filtros aos modelos de aplicação de página de /StreamedSingleFileUploadDb e /StreamedSingleFileUploadPhysical em Startup.ConfigureServices usando as convenções de Razor Pages.
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Como a vinculação de modelo não lê o formulário, os parâmetros que deveriam ser vinculados a partir do formulário não se vinculam (consulta, rota e cabeçalho continuam a funcionar). O método de ação funciona diretamente com a propriedade Request. Um MultipartReader é utilizado para ler cada seção. Os dados de chave/valor são armazenados em arquivo KeyValueAccumulator. Depois que as seções multipartes são lidas, o conteúdo do KeyValueAccumulator é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase para transmissão para uma base de dados com EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
O método completo StreamingController.UploadPhysical para transmitir para um local físico:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
No aplicativo de exemplo, as verificações de validação são tratadas pelo FileHelpers.ProcessStreamedFile.
Validation
A classe do aplicativo de exemplo demonstra várias verificações para uploads FileHelpers de arquivos em buffer IFormFile e transmitidos. Para processar IFormFile carregamentos de ficheiros em buffer na aplicação de exemplo, consulte o método ProcessFormFile no ficheiro Utilities/FileHelpers.cs. Para processar arquivos transmitidos, consulte o ProcessStreamedFile método no mesmo arquivo.
Warning
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não verificam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de verificação de vírus/malware é usada no arquivo antes de disponibilizá-lo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo prático de técnicas de validação, não implemente a classe FileHelpers num aplicativo de produção, a menos que:
- Compreenda completamente a implementação.
- Modifique a implementação conforme apropriado para o ambiente e as especificações do aplicativo.
Nunca implemente indiscriminadamente código de segurança em um aplicativo sem atender a esses requisitos.
Validação de conteúdo
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos é exigente em recursos de servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for diminuído devido à verificação de arquivos, considere descarregar o trabalho de verificação para um serviço em segundo plano, possivelmente um serviço em execução em um servidor diferente do servidor do aplicativo. Normalmente, os ficheiros carregados são mantidos numa área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo passa, ele é movido para o local normal de armazenamento de arquivos. Essas etapas geralmente são executadas em conjunto com um registro de banco de dados que indica o status de varredura de um arquivo. Ao usar essa abordagem, o aplicativo e o servidor do aplicativo permanecem focados em responder às solicitações.
Validação da extensão de arquivo
A extensão do ficheiro carregado deve ser verificada em relação a uma lista de extensões permitidas. Por exemplo:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Validação de assinatura de arquivo
A assinatura de um arquivo é determinada pelos primeiros bytes no início de um arquivo. Esses bytes podem ser usados para indicar se a extensão corresponde ao conteúdo do arquivo. O aplicativo de exemplo verifica assinaturas de arquivo para alguns tipos de arquivo comuns. No exemplo a seguir, a assinatura de arquivo para uma imagem JPEG é verificada em relação ao arquivo:
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Para obter assinaturas de arquivo adicionais, use um banco de dados de assinaturas de arquivo (resultado da pesquisa do Google) e especificações oficiais do arquivo. A consulta das especificações oficiais dos ficheiros pode garantir que as assinaturas selecionadas são válidas.
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 de Razor, utilize sempre o conteúdo do nome do ficheiro proveniente de uma solicitação de um utilizador.
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.
Validação de tamanho
Limite o tamanho dos ficheiros carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido via Configuração do ficheiro:
{
"FileSizeLimit": 2097152
}
O FileSizeLimit é injetado em PageModel classes:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Quando um tamanho de arquivo excede o limite, o arquivo é rejeitado:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Corresponder o valor do atributo name ao nome do parâmetro do método POST
Em formulários queRazor não enviam dados por POST ou usam JavaScript FormData diretamente para enviar dados, o nome especificado no elemento do formulário ou FormData deve corresponder ao nome do parâmetro na ação do controlador.
No exemplo a seguir:
Ao usar um
<input>elemento , onameatributo é definido como o valorbattlePlans:<input type="file" name="battlePlans" multiple>Ao usar
FormDataem JavaScript, o nome é definido com o valorbattlePlans:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Use um nome correspondente para o parâmetro do método C# (battlePlans):
Para um Razor método de manipulador de página Pages chamado
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)Para um método de ação do controlador MVC POST:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Configuração do servidor e do aplicativo
Limite de comprimento do corpo com várias partes
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo com várias partes. As seções de formulário que excedem esse limite lançam um InvalidDataException quando analisadas. O padrão é 134.217.728 (128 MB). Personalize o limite usando a MultipartBodyLengthLimit configuração em Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute é usado para definir o MultipartBodyLengthLimit para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Em um aplicativo Pages ou MVC Razor , aplique o filtro ao modelo de página ou método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel Tamanho máximo do corpo da solicitação
Para aplicativos hospedados pelo Kestrel, o tamanho máximo padrão do corpo da solicitação é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite usando a opção de servidor MaxRequestBodySizeKestrel :
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
});
RequestSizeLimitAttribute é usado para definir o MaxRequestBodySize para uma única página ou ação.
Numa Razor aplicação Páginas, aplique o filtro de acordo com uma convenção no Startup.ConfigureServices:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Em um Razor aplicativo de páginas ou um aplicativo MVC, aplique o filtro à classe ou ao método de ação do manipulador de página:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Outros Kestrel limites
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
IIS
O limite de solicitação padrão (maxAllowedContentLength) é de 30.000.000 bytes, o que equivale a aproximadamente 28,6 MB. Personalize o limite no ficheiro web.config. No exemplo a seguir, o limite é definido como 50 MB (52.428.800 bytes):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength configuração só se aplica ao IIS. Para obter mais informações, consulte Limites de <requestLimits>solicitação .
Aumente o tamanho máximo do corpo da solicitação para a solicitação HTTP definindo IISServerOptions.MaxRequestBodySize em Startup.ConfigureServices. No exemplo a seguir, o limite é definido como 50 MB (52.428.800 bytes):
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
Para obter mais informações, consulte Host ASP.NET Core no Windows com IIS.
Troubleshoot
Abaixo estão alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
Erro Não encontrado quando implantado em um servidor IIS
O erro a seguir indica que o arquivo carregado excede o comprimento de conteúdo configurado do servidor:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Para obter mais informações, consulte a seção do IIS.
Falha de ligação
Um erro de conexão e a redefinição da conexão do servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação de Kestrel. Para obter mais informações, consulte a seção Tamanho máximo do corpo daKestrel solicitação. Kestrel Os limites de conexão do cliente também podem exigir ajustes.
Exceção de referência nula com IFormFile
Se o controlador estiver a aceitar ficheiros carregados com IFormFile, mas o valor estiver como null, confirme se o formulário HTML está a especificar um valor enctype de multipart/form-data. Se esse atributo não estiver definido no elemento , o upload do arquivo não ocorrerá e todos os <form> argumentos associados IFormFile serão null. Confirme também se o nome de carregamento nos dados do formulário corresponde ao nome da aplicação.
O fluxo foi demasiado longo
Os exemplos neste tópico contam com MemoryStream para armazenar o conteúdo do ficheiro carregado. O limite de tamanho de a MemoryStream é int.MaxValue. Se o cenário de carregamento de arquivos do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de um único MemoryStream para armazenar o conteúdo de um arquivo carregado.