Como Fazer Upload de Arquivos em Razor Pages Sem Bibliotecas

Quando comecei a construir o Nerd Cult, precisei implementar upload de imagens para os posts. Sem bibliotecas externas, sĂł com o que o ASP.NET Core oferece. Foi um aprendizado — e hoje vou compartilhar tudo com vocĂȘ.

Quando comecei a construir o Nerd Cult, uma das primeiras funcionalidades que precisei implementar foi o upload de imagens para os posts. Parecia simples: o usuĂĄrio escolhe um arquivo, o servidor salva, a imagem aparece no post. Mas a realidade Ă© que tem muito mais coisa envolvida — e eu aprendi isso na prĂĄtica.

No inĂ­cio, eu queria uma solução rĂĄpida. Pesquisei bibliotecas, pacotes NuGet, tutoriais complicados. Mas aĂ­ percebi: o ASP.NET Core jĂĄ tem tudo o que preciso. A interface IFormFile, a validação de tamanho, as configuraçÔes de limite — tudo nativo. E o melhor: funciona sem depender de dependĂȘncias externas que podem quebrar com uma atualização.

Neste artigo, vou mostrar como implementar upload de arquivos em Razor Pages usando apenas os recursos nativos do ASP.NET Core. Vou passar por cada etapa: desde o formulårio HTML até a validação e salvamento no servidor. Tudo com a linguagem direta que eu gostaria de ter encontrado quando comecei.

O formulĂĄrio HTML: o ponto de partida

O primeiro passo Ă© o mais simples — e o mais fĂĄcil de errar. O formulĂĄrio HTML precisa de trĂȘs elementos para que o upload funcione:

  • Method="post" — o envio precisa ser POST.
  • enctype="multipart/form-data" — isso Ă© essencial. Sem ele, o arquivo nĂŁo Ă© enviado. O navegador sĂł envia texto, e o servidor nunca recebe o arquivo.
  • Input type="file" — o campo que permite selecionar o arquivo.

Aprendi isso na prĂĄtica

Passei horas debugando um upload que não funcionava porque esqueci o enctype. O formulårio enviava, o handler era chamado, mas o IFormFile vinha sempre null. Desde então, nunca mais esqueço.

Exemplo do formulĂĄrio que uso no Nerd Cult:

<form method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label asp-for="Imagem" class="control-label"></label>
        <input asp-for="Imagem" class="form-control" accept=".jpg,.jpeg,.png,.gif" />
        <span asp-validation-for="Imagem" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Enviar</button>
</form>

O atributo accept Ă© opcional, mas recomendo usar. Ele limita os arquivos mostrados no seletor do navegador — uma camada extra de conveniĂȘncia para o usuĂĄrio.

A Microsoft recomenda que o nome do input corresponda ao nome do parĂąmetro no handler. Se o input tem name="Imagem", o handler deve receber IFormFile Imagem.

O handler: recebendo o arquivo no servidor

No lado do servidor, a interface IFormFile é a nossa aliada. O ASP.NET Core faz o trabalho pesado: recebe o arquivo da requisição e encapsula ele num objeto que podemos manipular.

O que o IFormFile nos dĂĄ:

  • FileName — o nome original do arquivo
  • ContentType — o MIME type (ex: image/jpeg)
  • Length — o tamanho em bytes
  • CopyToAsync() — salva o arquivo no disco

O handler no Nerd Cult ficou assim:

public class CriarPostModel : PageModel
{
    private readonly IWebHostEnvironment _environment;

    public CriarPostModel(IWebHostEnvironment environment)
    {
        _environment = environment;
    }

    [BindProperty]
    public IFormFile Imagem { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        // Remove a validação de required do campo string (se houver)
        ModelState.Remove("Post.ImagemUrl");

        if (!ModelState.IsValid)
            return Page();

        if (Imagem != null && Imagem.Length > 0)
        {
            // Gera nome Ășnico para o arquivo
            var nomeArquivo = $"{Guid.NewGuid()}_{Path.GetFileName(Imagem.FileName)}";
            var caminho = Path.Combine(_environment.WebRootPath, "images", "posts", nomeArquivo);

            // Cria diretĂłrio se nĂŁo existir
            Directory.CreateDirectory(Path.GetDirectoryName(caminho));

            // Salva o arquivo
            using (var stream = new FileStream(caminho, FileMode.Create))
            {
                await Imagem.CopyToAsync(stream);
            }

            // Salva o nome no banco
            Post.ImagemUrl = $"/images/posts/{nomeArquivo}";
        }

        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }
}

Vou explicar cada parte:

  • IWebHostEnvironment — injetado para acessar o caminho fĂ­sico da aplicação.
  • Guid.NewGuid() — garante que o nome do arquivo seja Ășnico, evitando sobrescrita.
  • Directory.CreateDirectory — cria a pasta se ela nĂŁo existir.
  • FileStream + CopyToAsync — salva o arquivo no disco de forma assĂ­ncrona.

O uso de Guid.NewGuid() Ă© uma prĂĄtica recomendada pela Microsoft. Nunca confie no nome original do arquivo enviado pelo cliente.

Validação: tamanho, extensão e segurança

Depois de ver o upload funcionando, o próximo passo foi adicionar validaçÔes. Porque, se tem uma coisa que aprendi, é que não då para confiar em nada que vem do cliente.

Validando o tamanho do arquivo

A Microsoft recomenda que o limite de tamanho seja carregado de configuração, para facilitar ajustes sem recompilar o código.

No appsettings.json:

{
  "FileSizeLimit": 5242880  // 5 MB
}

E no handler:

private readonly long _fileSizeLimit;

public CriarPostModel(IConfiguration config, IWebHostEnvironment environment)
{
    _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    _environment = environment;
}

if (Imagem.Length > _fileSizeLimit)
{
    ModelState.AddModelError("Imagem", $"O arquivo excede o limite de {_fileSizeLimit / 1048576} MB");
    return Page();
}

Validando a extensĂŁo do arquivo

A extensão do arquivo é a primeira camada de validação de tipo. No Nerd Cult, eu só aceito imagens:

private readonly string[] _extensoesPermitidas = { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
private readonly string[] _mimeTypesPermitidos = { "image/jpeg", "image/png", "image/gif", "image/webp" };

if (Imagem != null && Imagem.Length > 0)
{
    var extensao = Path.GetExtension(Imagem.FileName).ToLowerInvariant();
    var mimeType = Imagem.ContentType.ToLowerInvariant();

    if (!_extensoesPermitidas.Contains(extensao) || !_mimeTypesPermitidos.Contains(mimeType))
    {
        ModelState.AddModelError("Imagem", "Tipo de arquivo nĂŁo permitido. Use JPG, PNG, GIF ou WEBP.");
        return Page();
    }
}

Dica de segurança

Valide tanto a extensão quanto o MIME type. Um usuårio mal-intencionado pode renomear um .exe para .jpg, e a extensão passaria. O MIME type é uma camada extra de proteção.

Configurando o limite de requisição

O ASP.NET Core tem limites padrĂŁo para o tamanho da requisição. Se seu arquivo ultrapassar 128 MB, a requisição Ă© rejeitada automaticamente. Para arquivos maiores, vocĂȘ pode aumentar o limite no Program.cs:

builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 268435456; // 256 MB
});

Onde salvar os arquivos? File System vs Banco de Dados

HĂĄ dois caminhos comuns para armazenar arquivos: no sistema de arquivos ou no banco de dados.

File System (o que uso no Nerd Cult)

As imagens dos posts do Nerd Cult ficam na pasta wwwroot/images/posts/. Apenas o nome do arquivo Ă© salvo no banco. Essa abordagem tem vantagens:

  • Simples de implementar: Copiar o arquivo para o disco Ă© trivial.
  • Performance: Servir arquivos estĂĄticos do disco Ă© muito eficiente.
  • Cache: O navegador pode fazer cache das imagens.

A desvantagem Ă© que fazer backup exige cuidar tanto do banco quanto dos arquivos.

Banco de Dados (como byte array)

Salvar o arquivo como byte[] no banco mantém tudo em um só lugar. Mas tem desvantagens:

  • Tamanho: Bancos de dados nĂŁo sĂŁo otimizados para grandes volumes de dados binĂĄrios.
  • Performance: Cada requisição de imagem puxa o blob do banco, o que Ă© mais lento que servir do disco.

Minha recomendação: use o sistema de arquivos para imagens e documentos que serĂŁo exibidos com frequĂȘncia. Use o banco para arquivos pequenos que fazem parte de uma entidade transacional.

Erros comuns que vocĂȘ vai encontrar (e como resolver)

Baseado na minha experiĂȘncia no Nerd Cult, aqui estĂŁo os erros mais comuns:

IFormFile vem null (ou Count == 0)

Causa mais comum: faltou o enctype="multipart/form-data" no formulĂĄrio. Sem isso, o navegador nĂŁo envia o arquivo como binĂĄrio.

Solução: verifique o formulårio HTML. Se estiver usando asp-page-handler, confirme que o nome do parùmetro no handler bate com o name do input.

Erro "Request Entity Too Large" (413)

Causa: O arquivo ultrapassou o limite configurado no servidor. Por padrão, o Kestrel aceita até 30 MB, e o MultipartBodyLengthLimit até 128 MB.

Solução: Aumente os limites nas configuraçÔes ou use o atributo [RequestSizeLimit] na pågina específica.

[RequestSizeLimit(104857600)] // 100 MB
[RequestFormLimits(MultipartBodyLengthLimit = 104857600)]
public class UploadModel : PageModel { ... }

Erro de permissĂŁo ao salvar o arquivo

Causa: A aplicação não tem permissão de escrita na pasta de destino.

Solução: Verifique as permissÔes da pasta wwwroot/images no servidor. No Windows, a conta do IIS precisa de permissão de escrita.

Conclusão: upload nativo, sem dor de cabeça

Implementar upload de arquivos no Nerd Cult foi um dos primeiros desafios que enfrentei. Na época, olhei para bibliotecas externas, pacotes NuGet, tutoriais complexos. Mas, no fim das contas, a solução mais simples foi a melhor: usar o que o ASP.NET Core jå oferece.

A interface IFormFile Ă© poderosa e fĂĄcil de usar. Com algumas linhas de cĂłdigo, vocĂȘ tem um sistema de upload funcionando — com validação, segurança e boas prĂĄticas. Sem bibliotecas externas, sem dependĂȘncias que podem quebrar com o tempo.

O que mais me surpreendeu nesse processo foi descobrir que o ASP.NET Core tem o buffering automĂĄtico para arquivos grandes, e que a Microsoft recomenda usar Path.GetRandomFileName() para garantir unicidade sem confiar no nome original.

E vocĂȘ, jĂĄ implementou upload no seu projeto? Quer compartilhar os desafios que enfrentou? Deixe seu comentĂĄrio abaixo — bora trocar ideia.

Quer mais dicas de ASP.NET Core?

No Nerd Cult, compartilho conhecimento prĂĄtico que aprendi construindo projetos reais. Assine nossa newsletter e receba conteĂșdos sobre desenvolvimento, carreira e cultura geek.

Quer contribuir com este blog?

Adoramos ouvir a opiniĂŁo dos nossos leitores! VocĂȘ jĂĄ implementou upload de arquivos em algum projeto? Qual foi sua maior dificuldade? Deixe seu comentĂĄrio abaixo com sua experiĂȘncia — suas dicas podem ajudar outros devs na mesma jornada. Sugira novos temas que vocĂȘ gostaria de ver no Nerd Cult — e se vocĂȘ quiser ver seu prĂłprio post publicado aqui, entre em contato conosco! Estamos sempre abertos a colaboraçÔes e histĂłrias inspiradoras da nossa comunidade.

Nerd Cult — onde o cĂłdigo encontra o rock, o cinema e a cultura geek. Porque ser nerd Ă© transformar o medo em curiosidade, e a curiosidade em poder.

#RazorPages #ASPNETCore #Upload #CSharp #NerdCult

Este conteĂșdo foi Ăștil para vocĂȘ?

ComentĂĄrios (0)

Nenhum comentĂĄrio ainda. Seja o primeiro a comentar!