Prevención de Duplicados en Cargas de Imágenes mediante Hashing

La gestión eficiente de archivos multimedia, como las imágenes, es crucial en muchas aplicaciones web. Un desafío común es evitar la subida repetida de archivos idénticos, lo que puede consumir espacio de almacenamiento innecesario y ralentizar el procesamiento. Una estrategia efectiva para abordar esto es utilizar el valor hash del contenido del archivo para identificar y prevenir duplicados antes de que se almacenen físicamente en el servidor.

Este enfoque implica calcular una huella digital única (hash) para cada archivo de imagen. Si un archivo con el mismo hash ya existe, se puede omitir la subida física y simplemente referenciar la imagen existente, optimizando así los recursos del servidor.

La interfaz de usuario incluye un elemento de imagen para mostrar el logo actual y un campo de entrada de archivo para seleccionar una nueva imagen. La carga se activa automáticamente al seleccionar un archivo, utilizando JavaScript para manejar la comunicación con el servidor de forma asíncrona.

/** * Función JavaScript para manejar la selección y carga del archivo. * Utiliza FormData para enviar el archivo al servidor mediante AJAX. */ fnuction iniciarCargaArchivo() { const inputElemento = document.getElementById('inputArchivoImagen'); if (inputElemento.files && inputElemento.files[0]) { const archivoSeleccionado = inputElemento.files[0]; const datosFormulario = new FormData(); datosFormulario.append('archivoImagen', archivoSeleccionado); // 'archivoImagen' es el nombre del campo esperado en el backend

                $.ajax({
                    url: '/Empresa/Panel/SubirLogo', // URL del endpoint en el servidor ASP.NET MVC
                    type: 'POST',
                    data: datosFormulario,
                    processData: false, // Importante: evita que jQuery procese los datos
                    contentType: false, // Importante: evita que jQuery establezca el tipo de contenido
                    dataType: 'json',
                    success: function(respuestaServidor) {
                        if (respuestaServidor && respuestaServidor.exito) {
                            $('#imagenMostrada').attr('src', respuestaServidor.rutaFinal);
                            console.log('Imagen subida o encontrada con éxito:', respuestaServidor.nombreArchivo);
                            // Opcional: Podrías actualizar otros campos en la página aquí.
                        } else {
                            console.error('Error al subir la imagen:', respuestaServidor.mensaje || 'Respuesta inesperada del servidor.');
                            alert('No se pudo subir la imagen: ' + (respuestaServidor.mensaje || 'Error desconocido.'));
                        }
                    },
                    error: function(jqXHR, estadoTexto, errorThrown) {
                        console.error('Fallo en la petición AJAX:', estadoTexto, errorThrown);
                        alert('Hubo un problema de red o del servidor al intentar subir la imagen.');
                    }
                });
            }
        }
    </script> </section> <section>Lógica del Servidor (Backend en C# ASP.NET MVC)

En el lado del servidor, se recibe el archivo subido, se calcula su hash MD5 y se utiliza este valor para determinar si el archivo ya existe. Si es nuevo, se guarda en una estructura de directorios basada en la fecha; de lo contrario, se referencia la versión existente.


using System;
using System.IO;
using System.Security.Cryptography;
using System.Web;
using System.Web.Mvc;

namespace ProyectoWeb.Controladores
{
   public class GestorArchivosController : Controller
   {
       // Ruta base para almacenar los logos subidos
       private const string RUTA_BASE_SUBIDAS = "/ArchivosSubidos/Logos";

       /// <summary>
       /// Procesa la subida de una imagen, verificando duplicados mediante hash MD5.
       /// </summary>
       /// <returns>JSON con el resultado de la operación (éxito, nombre, ruta y tamaño del archivo).</returns>
       [HttpPost]
       public ActionResult SubirLogo()
       {
           // Validar que se haya enviado un archivo
           if (Request.Files.Count == 0 || Request.Files[0] == null)
           {
               return Json(new { exito = false, mensaje = "No se ha proporcionado ningún archivo para subir." });
           }

           HttpPostedFileBase archivoRecibido = Request.Files[0];
           if (archivoRecibido.ContentLength == 0)
           {
               return Json(new { exito = false, mensaje = "El archivo enviado está vacío." });
           }

           // --- Paso 1: Calcular el hash MD5 del archivo ---
           string hashCalculado;
           using (MD5 algoritmoHash = MD5.Create())
           {
               // Asegurarse de que el stream del archivo esté al principio para un cálculo correcto del hash
               archivoRecibido.InputStream.Position = 0;
               byte[] bytesHash = algoritmoHash.ComputeHash(archivoRecibido.InputStream);
               // Convertir el array de bytes a una cadena hexadecimal, eliminando guiones y en minúsculas
               hashCalculado = BitConverter.ToString(bytesHash).Replace("-", "").ToLowerInvariant();
           }

           // Restablecer la posición del stream a cero en caso de que necesitemos leerlo de nuevo
           // (Aunque SaveAs suele manejar esto internamente, es una buena práctica)
           archivoRecibido.InputStream.Position = 0;

           // --- Paso 2: Determinar nombres y rutas ---
           string extensionOriginal = Path.GetExtension(archivoRecibido.FileName);
           string nombreArchivoFinal = hashCalculado + extensionOriginal; // El hash como nombre asegura unicidad

           string subDirectorioPorFecha = DateTime.Now.ToString("yyyyMMdd");
           string rutaVirtualDirectorio = Path.Combine(RUTA_BASE_SUBIDAS, subDirectorioPorFecha).Replace("\\", "/");
           string rutaFisicaDirectorio = Server.MapPath(rutaVirtualDirectorio);
           string rutaFisicaCompleta = Path.Combine(rutaFisicaDirectorio, nombreArchivoFinal);
           string rutaVirtualCompleta = Path.Combine(rutaVirtualDirectorio, nombreArchivoFinal).Replace("\\", "/");

           // --- Paso 3: Crear el directorio si no existe ---
           if (!Directory.Exists(rutaFisicaDirectorio))
           {
               Directory.CreateDirectory(rutaFisicaDirectorio);
           }

           // --- Paso 4: Guardar el archivo solo si no existe ya ---
           if (!System.IO.File.Exists(rutaFisicaCompleta))
           {
               archivoRecibido.SaveAs(rutaFisicaCompleta);
           }
           else
           {
               // El archivo ya existe, se detectó un duplicado. No se guarda de nuevo.
               System.Diagnostics.Debug.WriteLine($"Archivo duplicado detectado: {nombreArchivoFinal}. No se guardará nuevamente.");
           }

           // --- Paso 5: Retornar la información del archivo al cliente ---
           string tamanoFormateado = ObtenerTamanoArchivoFormateado(archivoRecibido.ContentLength);

           return Json(new 
           { 
               exito = true, 
               nombreArchivo = nombreArchivoFinal, 
               rutaFinal = rutaVirtualCompleta, 
               tamano = tamanoFormateado 
           });
       }

       /// <summary>
       /// Convierte el tamaño de un archivo en bytes a un formato legible (KB, MB, GB).
       /// </summary>
       /// <param name="bytes">Tamaño del archivo en bytes.</param>
       /// <returns>Cadena con el tamaño formateado.</returns>
       private string ObtenerTamanoArchivoFormateado(long bytes)
       {
           const int KB = 1024;
           const int MB = KB * 1024;
           const int GB = MB * 1024;

           if (bytes < KB)
           {
               return $"{bytes} B"; // Bytes
           }
           else if (bytes < MB)
           {
               return $"{(bytes / (double)KB):N2} KB"; // Kilobytes (2 decimales)
           }
           else if (bytes < GB)
           {
               return $"{(bytes / (double)MB):N2} MB"; // Megabytes (2 decimales)
           }
           else
           {
               return $"{(bytes / (double)GB):N2} GB"; // Gigabytes (2 decimales)
           }
       }
   }
}
       

Etiquetas: ASP.NET MVC C# JavaScript AJAX md5

Publicado el 9-2 10:09