ASP.NET WebForms 4.8: Optimización de carga de imágenes en GridView y problemas de memoria

21 views
Skip to first unread message

Kiquenet

unread,
Jul 6, 2026, 10:33:56 AMJul 6
to AltNet-Hispano

Tengo una aplicación ASP.NET WebForms 4.8 que muestra imágenes en un GridView. Los usuarios navegan rápidamente entre registros utilizando el teclado para visualizar las imágenes.

Situación actual
  • Aplicación ASP.NET Framework 4.8.
  • Aproximadamente 20 usuarios concurrentes.
  • Cada fila del GridView puede mostrar una imagen.
  • Los usuarios se desplazan continuamente por el grid con el teclado.
  • Estamos observando problemas de rendimiento y, en algunos casos, errores relacionados con consumo de memoria.


Evolución de la solución Versión inicial

Las imágenes se enviaban al navegador codificadas en Base64 dentro del HTML:

<img src="data:image/jpeg;base64,..." />


Problemas detectados:

  • Incremento considerable del tamaño de la página.
  • Mayor consumo de memoria en servidor y navegador.
  • Mayor tráfico de red.
  • La página se volvía lenta al navegar por muchos registros.



Versión actual

Las imágenes se sirven mediante un Handler (.ashx):

<img src="ImageHandler.ashx?id=123" />


La situación ha mejorado, pero seguimos teniendo dudas sobre posibles cuellos de botella y optimizaciones adicionales.



¿Qué mejoras recomendaríais para optimizar al máximo este escenario?

Aspectos que me interesan especialmente:

Servidor
  • Caché de imágenes (MemoryCache, Redis, etc.).
  • Configuración de caché HTTP.
  • Uso eficiente de streams.
  • Evitar cargar imágenes completas en memoria.
  • Configuración IIS para contenido estático.
  • Compresión.
Imágenes
  • Generar miniaturas y no cargar el tamaño original.
  • Conversión a formatos más eficientes (WebP, AVIF).
  • Redimensionado previo.
  • Calidad óptima para reducir peso.
Cliente
  • Lazy loading.
  • Precarga de imágenes adyacentes.
  • Virtualización del GridView.
  • Carga asíncrona.
  • Evitar recargas completas de página.
Arquitectura
  • CDN.
  • Almacenamiento en disco vs base de datos.
  • Uso de Output Cache.
  • Estrategias para escenarios con múltiples usuarios simultáneos.
Datos adicionales
  • Framework: ASP.NET WebForms 4.8.
  • Concurrentes: ~20 usuarios.
  • Las imágenes se consultan frecuentemente.
  • Actualmente las imágenes se sirven mediante Handler (.ashx).





Los 3 problemas más probables son:

  1. Imágenes servidas a resolución original

    • Es el error más común.
    • Si una imagen pesa 3 MB y en pantalla se ve a 200x200 px, se está transfiriendo y procesando información innecesaria.
    • Solución: generar miniaturas persistentes.
  2. Ausencia de caché HTTP

    • Si al moverse por el grid el navegador vuelve a solicitar imágenes ya vistas, se genera carga innecesaria en IIS y la base de datos.
    • Añadir:
    Cache-Control: public ETag Last-Modified Expires
  3. Imágenes almacenadas en Base de Datos y convertidas constantemente

    • Si el .ashx realiza continuamente:
    BBDD -> byte[] -> MemoryStream -> Response

    el GC tendrá mucha presión.

    • Mejor almacenar en disco u objeto storage y servir directamente cuando sea posible.
Mejoras con mayor impacto (ordenadas)
  1. Generar miniaturas optimizadas.
  2. Caché HTTP agresiva.
  3. Evitar Base64.
  4. Lazy loading.
  5. Precarga de la siguiente imagen y la anterior.
  6. WebP.
  7. Cache en memoria de imágenes más consultadas.
  8. Virtualización/paginación del GridView.
  9. Almacenar imágenes fuera de la BBDD.
  10. CDN (si hay usuarios remotos).

De todas ellas, miniaturas + caché HTTP + eliminar Base64 suelen proporcionar más del 80% de la mejora de rendimiento en este tipo de aplicaciones.



¿Alguien ha tenido experiencias similares o puede compartir qué optimizaciones tuvieron mayor impacto en consumo de memoria, velocidad de carga y escalabilidad?

Lecciones aprendidas ??

Kiquenet

unread,
Jul 7, 2026, 2:12:00 AMJul 7
to AltNet-Hispano



   #region Images
   if (!string.IsNullOrEmpty(ImagesSourcePath))
   {
       string sourcePath = ImageHelper.GetImagesPath(....);
       string pattern = ImageHelper.GetImagesPattern(...);

       string[] images = { };
       if (Directory.Exists(sourcePath))
       {
           images = Directory.GetFiles(sourcePath, pattern);
       }

       foreach (string image in images)
       {
           string stream = @"data:image/gif;base64," + Convert.ToBase64String(File.ReadAllBytes(image));
           vt.Images.Add(stream);
       }
   }
   else
   {
       Logger.Error(string.Format("No se pueden cargar las imágenes del ZZZZ. La ruta de origen de las imágenes es vacía"));
   }
   #endregion


Kiquenet

unread,
Jul 8, 2026, 2:25:30 AMJul 8
to AltNet-Hispano
Esto tiene varios inconvenientes:
  • Cargas el fichero completo en memoria (File.ReadAllBytes).
  • Lo vuelves a duplicar en memoria al convertirlo a Base64.
  • Base64 aumenta el tamaño aproximadamente un 33%.
  • El HTML generado crece enormemente.
  • El navegador no puede cachear fácilmente cada imagen de forma independiente.
  • Cada fila del GridView lleva embebidas todas las imágenes.

En una aplicación con 20 usuarios concurrentes esto puede disparar el uso de memoria tanto en IIS como en el navegador.


Prioridad 1: Sustituir Base64 por URLs
vt.Images.Add(
    $"ImageHandler.ashx?file={HttpUtility.UrlEncode(Path.GetFileName(image))}"
);



Lazy Loading

Si tienes muchas imágenes en el GridView:
Los navegadores modernos soportan:  loading="lazy" 

y no descargan la imagen hasta que entra en el viewport.

Image1.Attributes.Add("loading", "lazy");


Generar miniaturas automáticamente

Esta mejora suele ser la que más impacto tiene.

Situación actual

Supongamos:
Foto original = 4000 x 3000 Peso = 4 MB
<img width="200" height="150">

El navegador descarga igualmente los 4 MB




Opción recomendable

Generar una miniatura una única vez:

Originales/ 
              IMG001.jpg

 Thumbs/ 
              IMG001.jpg


Cuando no exista la miniatura:

using System.Drawing;

public static void CreateThumbnail(
    string source,
    string dest,
    int width)
{
    using (Image image = Image.FromFile(source))
    {
        int height = image.Height * width / image.Width;

        using (Bitmap bmp = new Bitmap(width, height))
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.DrawImage(image, 0, 0, width, height);

            bmp.Save(dest, ImageFormat.Jpeg);
        }
    }
}

La miniatura se genera una vez:

if (!File.Exists(thumbPath))
{
    CreateThumbnail(originalPath, thumbPath, 300);
}
Código Legacy: esto sobrecarga la página: e.Row.Attributes.Add("Mybusiness", JsonConvert.SerializeObject(vt)); e.Row.Attributes.Add("combocategories", ddlTechCategories.ClientID);




Kiquenet

unread,
Jul 9, 2026, 2:28:55 AMJul 9
to AltNet-Hispano

Implementar WebP

WebP suele reducir entre un 25% y un 50% respecto a JPG.

ASP.NET 4.8 no tiene soporte nativo.

La librería más habitual es:  Install-Package Magick.NET-Q8-AnyCPU

using ImageMagick;

using (var image = new MagickImage(source))

{
    image.Format = MagickFormat.WebP;
    image.Quality = 75;

    image.Write(destWebp);

}


IMG001.JPG  = 2,4 MB
IMG001.WEBP = 700 KB



Handler optimizado

Muchos handlers legacy hacen esto:


byte[] bytes = File.ReadAllBytes(path);
context.Response.BinaryWrite(bytes);

Mejor
context.Response.ContentType = "image/webp";

context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetMaxAge(TimeSpan.FromDays(30));

context.Response.TransmitFile(path);

TransmitFile() evita cargar el fichero completo en memoria del proceso.



Cache HTTP

Muy importante.

En tu .ashx:

context.Response.Cache.SetCacheability(    HttpCacheability.Public);

context.Response.Cache.SetExpires(    DateTime.Now.AddDays(30));

context.Response.Cache.SetMaxAge(    TimeSpan.FromDays(30));


Así cuando el usuario recorra el GridView:

Fila 1 -> Imagen A
Fila 2 -> Imagen B
Fila 1 -> Imagen A


la segunda vez la imagen sale de caché del navegador.



Precarga de imágenes cercanas

Si el usuario navega con el teclado:  Fila actual = 50
puedes precargar:

49
50
51
52


con JavaScript:

const preload = new Image();
preload.src = nextImageUrl;


La sensación de velocidad mejora mucho.



Virtualización

Si el GridView carga cientos de filas:

Mal:

500 filas
500 imágenes


Mejor:

20 filas visibles
Paginación
Carga incremental


En WebForms muchas veces basta con:

GridView1.AllowPaging = true;
GridView1.PageSize = 25;




Qué hacer en una aplicación ASP.NET 4.8 legacy

Ordenado por impacto:

1. Eliminar Base64 ✅

Tu código actual es el principal problema.

2. Miniaturas de 200-300 px ✅

Mantener originales aparte.

3. Convertir miniaturas a WebP ✅

Gran reducción de tráfico.

4. Handler con TransmitFile() ✅

Menor memoria en IIS.

5. Caché HTTP de 30 días ✅

Ahorro enorme de peticiones repetidas.

6. loading="lazy" ✅

Descarga sólo lo visible.

7. Paginación del GridView ✅

No renderizar cientos de imágenes a la vez.

Si tuviera que apostar, diría que el 80-90% de los problemas de memoria vienen del Base64 y de servir imágenes a resolución original.
Esas dos mejoras suelen producir la diferencia más grande en aplicaciones WebForms heredadas.


Reply all
Reply to author
Forward
0 new messages