Eventos
19 de nov., 23 - 21 de nov., 23
Participe de sessões online no Microsoft Ignite criadas para expandir suas habilidades e ajudá-lo a lidar com os problemas complexos de hoje.
Registrar agoraNão há mais suporte para esse navegador.
Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.
Por Rutger Storm
O ASP.NET Core oferece suporte ao upload de um ou mais arquivos usando vinculação de modelo com buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Exibir ou baixar código de exemplo (como baixar)
Tenha cuidado ao fornecer aos usuários a capacidade de fazer upload de arquivos para um servidor. Os ciberinvasores podem tentar:
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Aviso
Carregar códigos mal-intencionados em um sistema é frequentemente a primeira etapa para executar o código que pode:
Para obter informações de como reduzir as 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.
As opções comuns de armazenamento para arquivos incluem:
Banco de dados
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
O serviço de armazenamento de dados de nuvem, por exemplo, Armazenamento de Blobs do Azure.
Para obter mais informações, confira Início Rápido: usar o .NET para criar um blob no armazenamento de objetos.
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 ela possa lidar com os tamanhos esperados. Memória de parâmetro de comparação, CPU, disco e desempenho do banco de dados.
Embora limites específicos não possam ser fornecidos no que é pequeno versus grande para sua implantação, aqui estão alguns dos padrões relacionados do ASP.NET Core de FormOptions
(documentação da API):
HttpRequest.Form
não armazena em buffer todo o corpo da solicitação (BufferBody), mas armazena em buffer todos os arquivos de formulário de várias partes incluídos.MemoryBufferThreshold
atua como um limite entre arquivos pequenos e grandes, que são aumentados ou reduzidos dependendo dos recursos e cenários dos aplicativos.Para mais informações sobre FormOptions, consulte a classe FormOptions
na fonte de referência do ASP.NET Core.
Observação
Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Duas abordagens gerais para carregar arquivos são buffer e streaming.
de resposta
O arquivo inteiro é lido em um IFormFile. IFormFile
é uma representação C# do arquivo usado para processar ou salvar o arquivo.
O disco e a memória usados pelos uploads de arquivos dependem do número e do tamanho dos uploads de arquivos simultâneos. Se um aplicativo tentar armazenar em buffer muitos uploads, o site será bloqueado quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência de uploads de arquivos estiver esgotando os recursos do aplicativo, use streaming.
Qualquer arquivo armazenado em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
Arquivos temporários para solicitações maiores são gravados no local nomeado na variável de ASPNETCORE_TEMP
ambiente. Se ASPNETCORE_TEMP
não estiver definido, os arquivos serão gravados na pasta temporária do usuário atual.
O buffer de arquivos pequenos é abordado nas seções a seguir deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e processado ou salvo diretamente pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas por memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming.
Para fazer upload de arquivos pequenos, use um formulário com várias partes ou crie uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Páginas 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:
<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 são compatíveis com a Fetch API, use uma das seguintes abordagens:
Use um Polyfill fetch (por exemplo, polyfill window.fetch (github/fetch)).
Use 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 oferecer suporte a uploads de arquivos, os formulários HTML devem especificar um tipo de codificação (enctype
) de multipart/form-data
.
Para que um elemento de entrada files
dê suporte ao carregamento de vários arquivos, forneça o atributo multiple
no elemento <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple />
Os arquivos individuais carregados no servidor podem ser acessados por meio do Model Binding usando o IFormFile. O aplicativo de exemplo demonstra vários uploads de arquivos em buffer para cenários de armazenamento físico e de banco de dados.
Aviso
Não use a propriedade FileName
de IFormFile que não seja para exibição e registro em log. Ao exibir ou registrar em log, o HTML codifica o nome do arquivo. Um invasor cibernético pode fornecer um nome de arquivo mal-intencionado, incluindo caminhos completos ou caminhos relativos. Os aplicativos devem:
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
Ao carregar arquivos usando model binding e IFormFile, o método de ação pode aceitar:
Observação
A associação corresponde aos arquivos de formulário por nome. Por exemplo, o valor HTML name
em <input type="file" name="formFile">
deve corresponder ao parâmetro/propriedade C# associado (FormFile
). Para obter mais informações, consulte a seção Corresponder o valor do atributo de nome ao nome do parâmetro do método POST.
O exemplo a seguir:
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 FileStream deve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, será lançado um UnauthorizedAccessException no tempo de execução.
Os arquivos carregados usando a técnica IFormFile são armazenados em buffer na memória ou no disco do servidor antes do processamento. Dentro do método de ação, o conteúdo de IFormFile pode ser acessado como 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 Blobs do Azure.
Para obter outro exemplo que executa um loop em vários arquivos para upload e usa nomes de arquivo seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
no aplicativo de exemplo.
Aviso
Path.GetTempFileName gera um erro IOException se mais de 65.535 arquivos forem criados sem a exclusão dos 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:
Para armazenar dados de arquivos binários em um banco de dados usando o Entity Framework, defina uma propriedade Byte 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; }
}
Observação
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo vinculada. O exemplo anterior usa uma propriedade de modelo associada.
O FileUpload
é usado no Razor formulário Páginas:
<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 POSTed no servidor, copie o IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext
armazena o contexto de 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.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Aviso
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não se baseie nem confie na propriedade FileName
de IFormFile sem validação. A propriedade FileName
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
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 de ação processa diretamente os dados carregados, a vinculação do modelo de formulário é desativada por outro filtro personalizado. Dentro da ação, o conteúdo do formulário é lido usando um MultipartReader
, que lê cada MultipartSection
individual, processando o arquivo ou armazenando o conteúdo conforme apropriado. Depois que as seções de várias partes são lidas, a ação executa sua própria associação de modelo.
A resposta inicial da página carrega o formulário e salva um token antifalsificação em um cookie (por meio do atributo GenerateAntiforgeryTokenCookieAttribute
). O atributo usa o suporte anti-falsificação integrado 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 desabilitar a associaçã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 para os modelos de aplicativo de página de /StreamedSingleFileUploadDb
e /StreamedSingleFileUploadPhysical
em Startup.ConfigureServices
usando Razor convenções de Páginas:
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 associação de modelo não lê o formulário, os parâmetros associados do formulário não são associados (consulta, rota e cabeçalho continuam funcionando). O método de ação funciona diretamente com a propriedade Request
. Um MultipartReader
é usado para ler cada seção. Os dados de chave/valor são armazenados em um KeyValueAccumulator
. Depois que as seções de várias partes são lidas, o conteúdo da KeyValueAccumulator
é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase
para streaming para um banco 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 streaming 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 por FileHelpers.ProcessStreamedFile
.
A classe do aplicativo de FileHelpers
exemplo demonstra várias verificações de uploads de arquivos em buffer IFormFile e transmitidos. Para processar IFormFile uploads de arquivos em buffer no aplicativo de exemplo, consulte o ProcessFormFile
método no arquivo Utilities/FileHelpers.cs
. Para processar arquivos transmitidos, consulte o ProcessStreamedFile
método no mesmo arquivo.
Aviso
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não examinam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de scanner de vírus/malware é usada no arquivo antes de disponibilizar o arquivo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo funcional de técnicas de validação, não implemente a classe FileHelpers
em um aplicativo de produção, a menos que você:
Nunca implemente indiscriminadamente o código de segurança em um aplicativo sem atender a esses requisitos.
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos exige recursos do servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for reduzido 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 arquivos carregados são mantidos em uma área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo é passado, o arquivo é 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 verificação de um arquivo. Usando essa abordagem, o aplicativo e o servidor de aplicativos permanecem focados em responder às solicitações.
A extensão do arquivo 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
}
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 as 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 de arquivo oficiais. Consultar especificações de arquivo oficiais pode garantir que as assinaturas selecionadas sejam válidas.
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 codifica automaticamente valores de propriedade para exibição. O código a seguir é seguro de usar:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
Fora do Razor, sempre HtmlEncode o conteúdo do nome do arquivo da solicitação de um usuário.
Muitas implementações devem incluir um marcar que o arquivo existe; caso contrário, o arquivo será substituído por um arquivo de mesmo nome. Forneça lógica adicional para atender às especificações do aplicativo.
Limite o tamanho dos arquivos carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido por meio da Configuração do arquivo appsettings.json
:
{
"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
}
Em formuláriosRazor que postam dados ou usam javaScript FormData
diretamente, 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 elemento <input>
, o atributo name
é definido como o valor battlePlans
:
<input type="file" name="battlePlans" multiple>
Ao usar FormData
em JavaScript, o nome é definido como o valor battlePlans
:
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 método manipulador de Páginas de Razor página chamado Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Para um método de ação do controlador POST do MVC:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo de várias partes. Seções de formulário que excedem esse limite lançam um InvalidDataException quando analisado. O padrão é 134.217.728 (128 MB). Personalize o limite usando a configuração MultipartBodyLengthLimit 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.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em 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 Páginas Razor ou um aplicativo MVC, aplique o filtro ao modelo de página ou ao método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Para aplicativos hospedados por 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 MaxRequestBodySize para uma única página ou ação.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Em um aplicativo Páginas Razor ou em um aplicativo MVC, aplique o filtro à classe do manipulador de página ou ao método de ação:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
O RequestSizeLimitAttribute
também pode ser aplicado usando a @attribute
Razor diretiva :
@attribute [RequestSizeLimitAttribute(52428800)]
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
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 arquivo 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 configuração maxAllowedContentLength
se aplica somente ao IIS. Para obter mais informações, consulte Limites de solicitação<requestLimits>
.
Abaixo, são listados alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
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, confira a seção IIS.
Um erro de conexão e uma redefinição da conexão com o servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação do Kestrel. Para obter mais informações, consulte a seção Kestrel tamanho máximo do corpo da solicitação . Kestrel os limites de conexão do cliente também podem exigir ajuste.
Se o controlador estiver aceitando arquivos carregados usando IFormFile, mas o valor for null
, confirme se o formulário HTML está especificando um valor enctype
de multipart/form-data
. Se esse atributo não for definido no elemento <form>
, o upload do arquivo não ocorrerá e todos os argumentos vinculados IFormFile serão null
. Confirme também que a nomenclatura de upload nos dados do formulário corresponde à nomenclatura do aplicativo.
Os exemplos neste tópico dependem MemoryStream para conter o conteúdo do arquivo carregado. O limite de tamanho de um MemoryStream
é int.MaxValue
. Se o cenário de upload de arquivo do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de uma única MemoryStream
para armazenar o conteúdo de um arquivo carregado.
O ASP.NET Core oferece suporte ao upload de um ou mais arquivos usando vinculação de modelo com buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Exibir ou baixar código de exemplo (como baixar)
Tenha cuidado ao fornecer aos usuários a capacidade de fazer upload de arquivos para um servidor. Os ciberinvasores podem tentar:
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Aviso
Carregar códigos mal-intencionados em um sistema é frequentemente a primeira etapa para executar o código que pode:
Para obter informações de como reduzir as 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.
As opções comuns de armazenamento para arquivos incluem:
Banco de dados
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
Serviço de armazenamento de dados (por exemplo, Armazenamento de Blobs do Azure)
Para obter mais informações, confira Início Rápido: usar o .NET para criar um blob no armazenamento de objetos.
Duas abordagens gerais para carregar arquivos são buffer e streaming.
de resposta
O arquivo inteiro é lido em um IFormFile, que é uma representação C# do arquivo usado para processar ou salvar o arquivo.
Os recursos (disco, memória) usados pelos uploads de arquivos dependem do número e do tamanho dos uploads de arquivos simultâneos. Se um aplicativo tentar armazenar em buffer muitos uploads, o site será bloqueado quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência de uploads de arquivos estiver esgotando os recursos do aplicativo, use streaming.
Observação
Qualquer arquivo armazenado em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
O buffer de arquivos pequenos é abordado nas seções a seguir deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e processado ou salvo diretamente pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas por memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming.
Para fazer upload de arquivos pequenos, use um formulário com várias partes ou crie uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Páginas 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:
<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 são compatíveis com a Fetch API, use uma das seguintes abordagens:
Use um Polyfill fetch (por exemplo, polyfill window.fetch (github/fetch)).
Use 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 oferecer suporte a uploads de arquivos, os formulários HTML devem especificar um tipo de codificação (enctype
) de multipart/form-data
.
Para que um elemento de entrada files
dê suporte ao carregamento de vários arquivos, forneça o atributo multiple
no elemento <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Os arquivos individuais carregados no servidor podem ser acessados por meio do Model Binding usando o IFormFile. O aplicativo de exemplo demonstra vários uploads de arquivos em buffer para cenários de armazenamento físico e de banco de dados.
Aviso
Não use a propriedade FileName
de IFormFile que não seja para exibição e registro em log. Ao exibir ou registrar em log, o HTML codifica o nome do arquivo. Um invasor cibernético pode fornecer um nome de arquivo mal-intencionado, incluindo caminhos completos ou caminhos relativos. Os aplicativos devem:
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
Ao carregar arquivos usando model binding e IFormFile, o método de ação pode aceitar:
Observação
A associação corresponde aos arquivos de formulário por nome. Por exemplo, o valor HTML name
em <input type="file" name="formFile">
deve corresponder ao parâmetro/propriedade C# associado (FormFile
). Para obter mais informações, consulte a seção Corresponder o valor do atributo de nome ao nome do parâmetro do método POST.
O exemplo a seguir:
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 FileStream deve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, será lançado um UnauthorizedAccessException no tempo de execução.
Os arquivos carregados usando a técnica IFormFile são armazenados em buffer na memória ou no disco do servidor antes do processamento. Dentro do método de ação, o conteúdo de IFormFile pode ser acessado como 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 Blobs do Azure.
Para obter outro exemplo que executa um loop em vários arquivos para upload e usa nomes de arquivo seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
no aplicativo de exemplo.
Aviso
Path.GetTempFileName gera um erro IOException se mais de 65.535 arquivos forem criados sem a exclusão dos 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:
Para armazenar dados de arquivos binários em um banco de dados usando o Entity Framework, defina uma propriedade Byte 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; }
}
Observação
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo vinculada. O exemplo anterior usa uma propriedade de modelo associada.
O FileUpload
é usado no Razor formulário Páginas:
<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 POSTed no servidor, copie o IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext
armazena o contexto de 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.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Aviso
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não se baseie nem confie na propriedade FileName
de IFormFile sem validação. A propriedade FileName
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
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 de ação processa diretamente os dados carregados, a vinculação do modelo de formulário é desativada por outro filtro personalizado. Dentro da ação, o conteúdo do formulário é lido usando um MultipartReader
, que lê cada MultipartSection
individual, processando o arquivo ou armazenando o conteúdo conforme apropriado. Depois que as seções de várias partes são lidas, a ação executa sua própria associação de modelo.
A resposta inicial da página carrega o formulário e salva um token antifalsificação em um cookie (por meio do atributo GenerateAntiforgeryTokenCookieAttribute
). O atributo usa o suporte anti-falsificação integrado 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 desabilitar a associaçã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 para os modelos de aplicativo de página de /StreamedSingleFileUploadDb
e /StreamedSingleFileUploadPhysical
em Startup.ConfigureServices
usando Razor convenções de Páginas:
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 associação de modelo não lê o formulário, os parâmetros associados do formulário não são associados (consulta, rota e cabeçalho continuam funcionando). O método de ação funciona diretamente com a propriedade Request
. Um MultipartReader
é usado para ler cada seção. Os dados de chave/valor são armazenados em um KeyValueAccumulator
. Depois que as seções de várias partes são lidas, o conteúdo da KeyValueAccumulator
é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase
para streaming para um banco 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 streaming 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 por FileHelpers.ProcessStreamedFile
.
A classe do aplicativo de amostra FileHelpers
demonstra várias verificações para uploads de arquivos em buffer IFormFile e em fluxo contínuo. Para processar IFormFile uploads de arquivos em buffer no aplicativo de exemplo, consulte o ProcessFormFile
método no arquivo Utilities/FileHelpers.cs
. Para processar arquivos transmitidos, consulte o ProcessStreamedFile
método no mesmo arquivo.
Aviso
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não examinam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de scanner de vírus/malware é usada no arquivo antes de disponibilizar o arquivo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo funcional de técnicas de validação, não implemente a classe FileHelpers
em um aplicativo de produção, a menos que você:
Nunca implemente indiscriminadamente o código de segurança em um aplicativo sem atender a esses requisitos.
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos exige recursos do servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for reduzido 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 arquivos carregados são mantidos em uma área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo é passado, o arquivo é 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 verificação de um arquivo. Usando essa abordagem, o aplicativo e o servidor de aplicativos permanecem focados em responder às solicitações.
A extensão do arquivo 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
}
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 as 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 de arquivo oficiais. Consultar especificações de arquivo oficiais pode garantir que as assinaturas selecionadas sejam válidas.
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 codifica automaticamente valores de propriedade para exibição. O código a seguir é seguro de usar:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
Fora do Razor, sempre HtmlEncode o conteúdo do nome do arquivo da solicitação de um usuário.
Muitas implementações devem incluir um marcar que o arquivo existe; caso contrário, o arquivo será substituído por um arquivo de mesmo nome. Forneça lógica adicional para atender às especificações do aplicativo.
Limite o tamanho dos arquivos carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido por meio da Configuração do arquivo appsettings.json
:
{
"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
}
Em formuláriosRazor que postam dados ou usam javaScript FormData
diretamente, 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 elemento <input>
, o atributo name
é definido como o valor battlePlans
:
<input type="file" name="battlePlans" multiple>
Ao usar FormData
em JavaScript, o nome é definido como o valor battlePlans
:
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 método manipulador de Páginas de Razor página chamado Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Para um método de ação do controlador POST do MVC:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo de várias partes. Seções de formulário que excedem esse limite lançam um InvalidDataException quando analisado. O padrão é 134.217.728 (128 MB). Personalize o limite usando a configuração MultipartBodyLengthLimit 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.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em 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 Páginas Razor ou um aplicativo MVC, aplique o filtro ao modelo de página ou ao método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Para aplicativos hospedados por 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 MaxRequestBodySize para uma única página ou ação.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Em um aplicativo Páginas Razor ou em um aplicativo MVC, aplique o filtro à classe do manipulador de página ou ao método de ação:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
O RequestSizeLimitAttribute
também pode ser aplicado usando a @attribute
Razor diretiva :
@attribute [RequestSizeLimitAttribute(52428800)]
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
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 arquivo 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 configuração maxAllowedContentLength
se aplica somente ao IIS. Para obter mais informações, consulte Limites de solicitação<requestLimits>
.
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 Hospedar o ASP.NET Core no Windows com o IIS.
Abaixo, são listados alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
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, confira a seção IIS.
Um erro de conexão e uma redefinição da conexão com o servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação do Kestrel. Para obter mais informações, consulte a seção Kestrel tamanho máximo do corpo da solicitação . Kestrel os limites de conexão do cliente também podem exigir ajuste.
Se o controlador estiver aceitando arquivos carregados usando IFormFile, mas o valor for null
, confirme se o formulário HTML está especificando um valor enctype
de multipart/form-data
. Se esse atributo não for definido no elemento <form>
, o upload do arquivo não ocorrerá e todos os argumentos vinculados IFormFile serão null
. Confirme também que a nomenclatura de upload nos dados do formulário corresponde à nomenclatura do aplicativo.
Os exemplos neste tópico dependem MemoryStream para conter o conteúdo do arquivo carregado. O limite de tamanho de um MemoryStream
é int.MaxValue
. Se o cenário de upload de arquivo do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de uma única MemoryStream
para armazenar o conteúdo de um arquivo carregado.
O ASP.NET Core oferece suporte ao upload de um ou mais arquivos usando vinculação de modelo com buffer para arquivos menores e streaming sem buffer para arquivos maiores.
Exibir ou baixar código de exemplo (como baixar)
Tenha cuidado ao fornecer aos usuários a capacidade de fazer upload de arquivos para um servidor. Os ciberinvasores podem tentar:
As etapas de segurança que reduzem a probabilidade de um ataque bem-sucedido são:
†O aplicativo de exemplo demonstra uma abordagem que atende aos critérios.
Aviso
Carregar códigos mal-intencionados em um sistema é frequentemente a primeira etapa para executar o código que pode:
Para obter informações de como reduzir as 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.
As opções comuns de armazenamento para arquivos incluem:
Banco de dados
Armazenamento físico (sistema de arquivos ou compartilhamento de rede)
Serviço de armazenamento de dados (por exemplo, Armazenamento de Blobs do Azure)
Para obter mais informações, confira Início Rápido: usar o .NET para criar um blob no armazenamento de objetos. O tópico demonstra , mas UploadFromFileAsync pode ser usado para salvar um UploadFromStreamAsync no armazenamento de blobs ao trabalhar com um FileStream.Stream
Duas abordagens gerais para carregar arquivos são buffer e streaming.
de resposta
O arquivo inteiro é lido em um IFormFile, que é uma representação C# do arquivo usado para processar ou salvar o arquivo.
Os recursos (disco, memória) usados pelos uploads de arquivos dependem do número e do tamanho dos uploads de arquivos simultâneos. Se um aplicativo tentar armazenar em buffer muitos uploads, o site será bloqueado quando ficar sem memória ou espaço em disco. Se o tamanho ou a frequência de uploads de arquivos estiver esgotando os recursos do aplicativo, use streaming.
Observação
Qualquer arquivo armazenado em buffer que exceda 64 KB é movido da memória para um arquivo temporário no disco.
O buffer de arquivos pequenos é abordado nas seções a seguir deste tópico:
Streaming
O arquivo é recebido de uma solicitação de várias partes e processado ou salvo diretamente pelo aplicativo. O streaming não melhora significativamente o desempenho. O streaming reduz as demandas por memória ou espaço em disco ao carregar arquivos.
O streaming de arquivos grandes é abordado na seção Carregar arquivos grandes com streaming.
Para fazer upload de arquivos pequenos, use um formulário com várias partes ou crie uma solicitação POST usando JavaScript.
O exemplo a seguir demonstra o uso de um Razor formulário Páginas 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:
<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 são compatíveis com a Fetch API, use uma das seguintes abordagens:
Use um Polyfill fetch (por exemplo, polyfill window.fetch (github/fetch)).
Use 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 oferecer suporte a uploads de arquivos, os formulários HTML devem especificar um tipo de codificação (enctype
) de multipart/form-data
.
Para que um elemento de entrada files
dê suporte ao carregamento de vários arquivos, forneça o atributo multiple
no elemento <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Os arquivos individuais carregados no servidor podem ser acessados por meio do Model Binding usando o IFormFile. O aplicativo de exemplo demonstra vários uploads de arquivos em buffer para cenários de armazenamento físico e de banco de dados.
Aviso
Não use a propriedade FileName
de IFormFile que não seja para exibição e registro em log. Ao exibir ou registrar em log, o HTML codifica o nome do arquivo. Um invasor cibernético pode fornecer um nome de arquivo mal-intencionado, incluindo caminhos completos ou caminhos relativos. Os aplicativos devem:
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
Ao carregar arquivos usando model binding e IFormFile, o método de ação pode aceitar:
Observação
A associação corresponde aos arquivos de formulário por nome. Por exemplo, o valor HTML name
em <input type="file" name="formFile">
deve corresponder ao parâmetro/propriedade C# associado (FormFile
). Para obter mais informações, consulte a seção Corresponder o valor do atributo de nome ao nome do parâmetro do método POST.
O exemplo a seguir:
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 FileStream deve incluir o nome do arquivo. Se o nome do arquivo não for fornecido, será lançado um UnauthorizedAccessException no tempo de execução.
Os arquivos carregados usando a técnica IFormFile são armazenados em buffer na memória ou no disco do servidor antes do processamento. Dentro do método de ação, o conteúdo de IFormFile pode ser acessado como 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 Blobs do Azure.
Para obter outro exemplo que executa um loop em vários arquivos para upload e usa nomes de arquivo seguros, consulte Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
no aplicativo de exemplo.
Aviso
Path.GetTempFileName gera um erro IOException se mais de 65.535 arquivos forem criados sem a exclusão dos 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:
Para armazenar dados de arquivos binários em um banco de dados usando o Entity Framework, defina uma propriedade Byte 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; }
}
Observação
IFormFile pode ser usado diretamente como um parâmetro de método de ação ou como uma propriedade de modelo vinculada. O exemplo anterior usa uma propriedade de modelo associada.
O FileUpload
é usado no Razor formulário Páginas:
<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 POSTed no servidor, copie o IFormFile para um fluxo e salve-o como uma matriz de bytes no banco de dados. No exemplo a seguir, _dbContext
armazena o contexto de 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.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Aviso
Tenha cuidado ao armazenar dados binários em bancos de dados relacionais, pois isso pode afetar negativamente o desempenho.
Não se baseie nem confie na propriedade FileName
de IFormFile sem validação. A propriedade FileName
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 as considerações de segurança. Informações adicionais são fornecidas pelas seções a seguir e pelo aplicativo de exemplo:
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 de ação processa diretamente os dados carregados, a vinculação do modelo de formulário é desativada por outro filtro personalizado. Dentro da ação, o conteúdo do formulário é lido usando um MultipartReader
, que lê cada MultipartSection
individual, processando o arquivo ou armazenando o conteúdo conforme apropriado. Depois que as seções de várias partes são lidas, a ação executa sua própria associação de modelo.
A resposta inicial da página carrega o formulário e salva um token antifalsificação em um cookie (por meio do atributo GenerateAntiforgeryTokenCookieAttribute
). O atributo usa o suporte anti-falsificação integrado 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 desabilitar a associaçã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 para os modelos de aplicativo de página de /StreamedSingleFileUploadDb
e /StreamedSingleFileUploadPhysical
em Startup.ConfigureServices
usando Razor convenções de Páginas:
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 associação de modelo não lê o formulário, os parâmetros associados do formulário não são associados (consulta, rota e cabeçalho continuam funcionando). O método de ação funciona diretamente com a propriedade Request
. Um MultipartReader
é usado para ler cada seção. Os dados de chave/valor são armazenados em um KeyValueAccumulator
. Depois que as seções de várias partes são lidas, o conteúdo da KeyValueAccumulator
é usado para vincular os dados do formulário a um tipo de modelo.
O método completo StreamingController.UploadDatabase
para streaming para um banco 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 streaming 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 por FileHelpers.ProcessStreamedFile
.
A classe do aplicativo de amostra FileHelpers
demonstra várias verificações para uploads de arquivos em buffer IFormFile e em fluxo contínuo. Para processar IFormFile uploads de arquivos em buffer no aplicativo de exemplo, consulte o ProcessFormFile
método no arquivo Utilities/FileHelpers.cs
. Para processar arquivos transmitidos, consulte o ProcessStreamedFile
método no mesmo arquivo.
Aviso
Os métodos de processamento de validação demonstrados no aplicativo de exemplo não examinam o conteúdo dos arquivos carregados. Na maioria dos cenários de produção, uma API de scanner de vírus/malware é usada no arquivo antes de disponibilizar o arquivo para usuários ou outros sistemas.
Embora o exemplo de tópico forneça um exemplo funcional de técnicas de validação, não implemente a classe FileHelpers
em um aplicativo de produção, a menos que você:
Nunca implemente indiscriminadamente o código de segurança em um aplicativo sem atender a esses requisitos.
Use uma API de verificação de vírus/malware de terceiros no conteúdo carregado.
A verificação de arquivos exige recursos do servidor em cenários de alto volume. Se o desempenho do processamento de solicitações for reduzido 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 arquivos carregados são mantidos em uma área em quarentena até que o verificador de vírus em segundo plano os verifique. Quando um arquivo é passado, o arquivo é 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 verificação de um arquivo. Usando essa abordagem, o aplicativo e o servidor de aplicativos permanecem focados em responder às solicitações.
A extensão do arquivo 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
}
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 as 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 de arquivo oficiais. Consultar especificações de arquivo oficiais pode garantir que as assinaturas selecionadas sejam válidas.
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 codifica automaticamente valores de propriedade para exibição. O código a seguir é seguro de usar:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
Fora do Razor, sempre HtmlEncode o conteúdo do nome do arquivo da solicitação de um usuário.
Muitas implementações devem incluir um marcar que o arquivo existe; caso contrário, o arquivo será substituído por um arquivo de mesmo nome. Forneça lógica adicional para atender às especificações do aplicativo.
Limite o tamanho dos arquivos carregados.
No aplicativo de exemplo, o tamanho do arquivo é limitado a 2 MB (indicado em bytes). O limite é fornecido por meio da Configuração do arquivo appsettings.json
:
{
"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
}
Em formuláriosRazor que postam dados ou usam javaScript FormData
diretamente, 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 elemento <input>
, o atributo name
é definido como o valor battlePlans
:
<input type="file" name="battlePlans" multiple>
Ao usar FormData
em JavaScript, o nome é definido como o valor battlePlans
:
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 método manipulador de Páginas de Razor página chamado Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Para um método de ação do controlador POST do MVC:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit define o limite para o comprimento de cada corpo de várias partes. Seções de formulário que excedem esse limite lançam um InvalidDataException quando analisado. O padrão é 134.217.728 (128 MB). Personalize o limite usando a configuração MultipartBodyLengthLimit 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.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em 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 Páginas Razor ou um aplicativo MVC, aplique o filtro ao modelo de página ou ao método de ação:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Para aplicativos hospedados por 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 MaxRequestBodySize para uma única página ou ação.
Em um aplicativo Páginas Razor, aplique o filtro com uma convenção de em 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 aplicativo Páginas Razor ou em um aplicativo MVC, aplique o filtro à classe do manipulador de página ou ao método de ação:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Outros Kestrel limites podem ser aplicados a aplicativos hospedados por Kestrel:
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 arquivo 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 configuração maxAllowedContentLength
se aplica somente ao IIS. Para obter mais informações, consulte Limites de solicitação<requestLimits>
.
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 Hospedar o ASP.NET Core no Windows com o IIS.
Abaixo, são listados alguns problemas comuns encontrados ao trabalhar com o upload de arquivos e suas possíveis soluções.
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, confira a seção IIS.
Um erro de conexão e uma redefinição da conexão com o servidor provavelmente indicam que o arquivo carregado excede o tamanho máximo do corpo da solicitação do Kestrel. Para obter mais informações, consulte a seção Kestrel tamanho máximo do corpo da solicitação . Kestrel os limites de conexão do cliente também podem exigir ajuste.
Se o controlador estiver aceitando arquivos carregados usando IFormFile, mas o valor for null
, confirme se o formulário HTML está especificando um valor enctype
de multipart/form-data
. Se esse atributo não for definido no elemento <form>
, o upload do arquivo não ocorrerá e todos os argumentos vinculados IFormFile serão null
. Confirme também que a nomenclatura de upload nos dados do formulário corresponde à nomenclatura do aplicativo.
Os exemplos neste tópico dependem MemoryStream para conter o conteúdo do arquivo carregado. O limite de tamanho de um MemoryStream
é int.MaxValue
. Se o cenário de upload de arquivo do aplicativo exigir a retenção de conteúdo de arquivo maior que 50 MB, use uma abordagem alternativa que não dependa de uma única MemoryStream
para armazenar o conteúdo de um arquivo carregado.
Comentários do ASP.NET Core
O ASP.NET Core é um projeto código aberto. Selecione um link para fornecer comentários:
Eventos
19 de nov., 23 - 21 de nov., 23
Participe de sessões online no Microsoft Ignite criadas para expandir suas habilidades e ajudá-lo a lidar com os problemas complexos de hoje.
Registrar agora