Armazenamento de Imagens em Banco de Dados com ASP.NET Core

Este artigo aborda a persistência de arquivos de imagem diretamente em uma base de dados relacional, utilizando MySQL como sistema de armazenamento e ASP.NET Core como plataforma de desenvolvimento.

Estrutura da Tabela para Armazenamento Binário

Para guardar dados binários de imagens, o MySQL oferece tipos específicos com diferentes limites de capacidade:

Tipo Tamanho Máximo
TINYBLOB 255 bytes
BLOB 65 KB
MEDIUMBLOB 16 MB
LONGBLOB 4 GB

Para a maioria dos cenários com fotos de média resolução, MEDIUMBLOB é adequado. A definição da tabela:

CREATE TABLE galeria_imagens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    entidade_id INT NOT NULL,
    dados_imagem MEDIUMBLOB NOT NULL,
    ativo BOOLEAN DEFAULT TRUE,
    criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Interface de Upload no Frontend

O formulário HTML utiliza enctype="multipart/form-data" para permitir o envio de arquivos:

<form method="post" enctype="multipart/form-data" asp-action="SalvarGaleria">
    <div class="upload-area">
        <label for="arquivosFotos" class="botao-upload">
            <input type="file" 
                   accept="image/*" 
                   name="ArquivosFotos" 
                   id="arquivosFotos" 
                   multiple 
                   class="input-oculto"/>
            <img src="/assets/icone-upload.svg" alt="Adicionar fotos"/>
        </label>
    </div>
    <div id="previsualizacao" class="container-miniaturas"></div>
</form>

Modelo e Processamento no Backend

A propriedade para receber os arquivos na classe de modelo:

public class GaleriaViewModel
{
    public List<IFormFile> ArquivosFotos { get; set; }
    public List<byte[]> DadosImagensProcessadas { get; set; }
}

O controller recebe os dados e inicia o processamento:

[HttpPost]
public async Task<IActionResult> SalvarGaleria(GaleriaViewModel dados)
{
    var resultado = new ResultadoOperacao();
    
    dados.DadosImagensProcessadas = await GerarMiniaturas(
        dados.ArquivosFotos, 
        largura: 256, 
        altura: 256
    );
    
    resultado.Sucesso = await _servicoGaleria.PersistirImagens(dados);
    return Json(resultado);
}

Geração de Miniaturas em Paralelo

O processamento de múltiplas imagens utiliza paralelismo para melhor performence:

private async Task<List<byte[]>> GerarMiniaturas(
    List<IFormFile> arquivos, 
    int largura, 
    int altura)
{
    var miniaturas = new ConcurrentBag<byte[]>();
    
    if (arquivos == null || arquivos.Count == 0)
        return new List<byte[]>();

    var opcoesParalelas = new ParallelOptions 
    { 
        MaxDegreeOfParallelism = Environment.ProcessorCount / 2 
    };

    Parallel.ForEach(arquivos.Take(12), opcoesParalelas, arquivo =>
    {
        try
        {
            if (arquivo?.Length > 0)
            {
                using var fluxoEntrada = new MemoryStream();
                arquivo.CopyTo(fluxoEntrada);
                fluxoEntrada.Position = 0;

                using var imagemOriginal = Image.FromStream(fluxoEntrada);
                using var imagemRedimensionada = new Bitmap(largura, altura);
                
                using (var graficos = Graphics.FromImage(imagemRedimensionada))
                {
                    graficos.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    graficos.DrawImage(
                        imagemOriginal, 
                        new Rectangle(0, 0, largura, altura)
                    );
                }

                using var fluxoSaida = new MemoryStream();
                imagemRedimensionada.Save(fluxoSaida, ImageFormat.Jpeg);
                miniaturas.Add(fluxoSaida.ToArray());
            }
        }
        catch (Exception erro)
        {
            _registrador.LogError(erro, "Falha ao processar imagem");
        }
    });

    return miniaturas.ToList();
}

Persistência com Dapper

A inserção das imagens processadas na base de dados:

public async Task<bool> PersistirImagens(GaleriaViewModel dados)
{
    const string sqlInsercao = @"
        INSERT INTO galeria_imagens (entidade_id, dados_imagem, ativo) 
        VALUES (@idEntidade, @bytesImagem, @estaAtivo);
        SELECT LAST_INSERT_ID();";

    using var conexao = ObterConexao();
    await conexao.OpenAsync();
    
    using var transacao = conexao.BeginTransaction();
    try
    {
        foreach (var bytesImagem in dados.DadosImagensProcessadas)
        {
            var parametros = new 
            {
                idEntidade = dados.EntidadeId,
                bytesImagem = bytesImagem,
                estaAtivo = true
            };
            
            await conexao.ExecuteAsync(
                sqlInsercao, 
                parametros, 
                transacao
            );
        }
        
        transacao.Commit();
        return true;
    }
    catch (Exception erro)
    {
        _registrador.LogError(erro, "Erro na persistência das imagens");
        transacao.Rollback();
        throw;
    }
}

Recuperação e Exibição

Para apresentar as imagens armazenadas, converte-se os bytes para Base64:

public List<string> ConverterParaBase64(List<byte[]> imagensBinarias)
{
    return imagensBinarias
        .Select(bytes => Convert.ToBase64String(bytes))
        .ToList();
}

Na view Razor, a exibição utiliza Data URI:

<div class="galeria-fotos">
    @foreach (var imagemBase64 in Model.ImagensEmBase64)
    {
        <div class="item-galeria" 
             style="background-image: url('data:image/jpeg;base64,@imagemBase64')">
        </div>
    }
</div>

Considerações sobre Performance

Armazenar imagens diretamente no banco de dados oferece integridade referencial e backup simplificdao, porém aumenta o tamanho da base de dados. Para aplicações com grande volume de mídia, considere:

  • Armazenar apenas metadados no banco e arquivos em serviço de storage (S3, Azure Blob)
  • Implementar cache em CDN para imagens frequentemente acessadas
  • Comprimir imagens antes da persistência
  • Limitar dimensões máximas aceitas no upload

Tags: ASP.NET Core MySQL Dapper IFormFile Image Processing

Publicado em 8-17 03:45