Solución de Carga Masiva de Múltiples Archivos en la Web con Plupload

Plupload es un componente de carga de archivos para navegadores web que ofrece una interfaz amigable, muestra el progreso de la carga, permite la previsualización automática de imágenes y la carga en fragmentos, facilitando la transferencia simultánea de varios archivos. Utiliza diversos motores de carga como Flash, Silverlight, HTML5, Gears, BrowserPlus o la carga de archivos estándar.

Desarrollado por los creadores de TinyMCE, Plupload se integra fácilmente en sistemas de gestión de contanido y aplicaciones similares. Actualmente, se compone de una API central y un componente de cola de carga basado en jQuery, lo que permite su uso directo o la creación de implementaciones personalizadas. Plupload se distribuye bajo la licencia GPLv2.

Funcionalidades de Plupload:

Característica Flash Gears HTML 5 Silverlight BrowserPlus HTML 4
Carga en Fragmentos √¹ X
Arrastrar y Soltar X √² X X
Redimensionar PNG √³ X
Redimensionar JPEG √³ X
Filtrado de Tipos √⁴ X
Carga en Streaming X X
Carga Multipart √⁵
Restricción Tamaño X
Progreso de Carga X
Cabeceras Personaliz. X X

Notas sobre las funcionalidades:

  1. La carga en fragmentos está bien soportada en Chrome y en la mayoría de Firefox 4+.
  2. La funcionalidad de arrastrar y soltar archivos solo está disponible en Firefox y WebKit. Safari en Windows presenta problemas que requieren soluciones alternativas.
  3. El redimensionamiento de imágenes solo es posible en Firefox 3.5+ (con calidad fija) y Chrome. Safari/Opera no permiten acceso directo a los datos de los archivos seleccionados.
  4. El filtrado de tipos de archivo no es compatible con todos los navegadores. Sin embargo, se utiliza el atributo accept de HTML5, por lo que funcionará cuando los navegadores lo soporten.
  5. La carga multipart solo es compatible con Gecko y WebKit.

Características Clave de Plupload:

  • Utiliza el componente jQuery para la selección y gestión de la cola de archivos.
  • Emplea diversos motores de carga: Flash, Silverlight, HTML5, Gears, BrowserPlus y carga de archivos estándar.
  • Permite el uso de la API central de Plupload para personalizar la selección y carga de archivos.
  • El diálogo de selección de archivos, activado por JavaScript, puede configurarse para permitir la selección de uno o múltiples archivos, y restringir los tipos de archivo permitidos (ej. jpg, gif).
  • Ofrece la posibilidad de personalizar eventos durante el proceso de carga.
  • La carga de archivos seleccionados es independiente de la página y el formulario, permitiendo un procesamiento más sencillo de cada archivo individualmente en el servidor. Para más detalles, visite el sitio oficial de Plupload: http://www.plupload.com/

Implementación de Carga Masiva con Plupload

A continuación, se presenta una solución para permitir a los usuarios seleccionar múltiples archivos y cargarlos al servidor utilizando el componente Plupload.

Interfaz de Usuario:

  1. Visualización Inicial: Muestra la interfaz lista para la carga.
  2. Selección de Múltiples Archivos: Permite al usuario elegir varios archivos a la vez.
  3. Progreso de Carga: Muestra el avance de la carga de cada archivo.

Código y Pasos Detallados

Paso 1: Preparación del Componente PluploadDescargue el componente Plupload y sus demos desde el sitio oficial: http://www.plupload.com/

Paso 2: Estructura de Directorios de la AplicaciónOrganice los archivos de su proyecto según la siguiente estructura:

  1. Estructura Principal:Estructura de directorios
  2. Scripts de Carga de Archivos: Incluya los archivos JavaScript necesarios.
  3. Directorio jquery.ui.plupload: Contiene los archivos para la interfaz visual del componente.

**Paso 3: Configuración del Frontend (Página Web)**Cree la interfaz web para la interacción del usuario.

Archivo WebUploadFileTest2.aspx (Frontend):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebUploadFileTest2.aspx.cs" Inherits="WebApplication1.WebUploadFileTest2" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Carga Múltiple de Archivos</title>
    <style type="text/css">@import url(Scripts/jquery.ui.plupload/css/jquery.ui.plupload.css);</style>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/jquery-ui.css"/>
    <script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>
    <script type="text/javascript" src="Scripts/plupload.full.js"></script>
    <script type="text/javascript" src="Scripts/jquery.ui.plupload/jquery.ui.plupload.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#uploader").plupload({
                runtimes: 'gears,flash,silverlight,browserplus,html5', // Motores de carga a utilizar
                url: 'uploadFiles.ashx', // Endpoint del servidor para la carga
                max_file_size: '10mb', // Límite máximo de tamaño por archivo
                chunk_size: '1mb', // Tamaño de cada fragmento de carga
                unique_names: true, // Generar nombres de archivo únicos en el servidor

                // Opciones para redimensionamiento de imágenes en cliente (si está habilitado)
                // resize: { width: 320, height: 240, quality: 90 },

                // Filtros para tipos de archivo permitidos
                filters: [
                    { title: "Archivos de Imagen", extensions: "jpg,gif,png" },
                    { title: "Archivos Zip", extensions: "zip,rar,7z" }
                ],

                // Configuración para el runtime Flash
                flash_swf_url: 'Scripts/plupload.flash.swf',

                // Configuración para el runtime Silverlight
                silverlight_xap_url: 'Scripts/plupload.silverlight.xap'
            });

            // Validación del formulario en el cliente
            $('form').submit(function (e) {
                var uploader = $('#uploader').plupload('getUploader');

                if (uploader.files.length > 0) {
                    uploader.bind('StateChanged', function () {
                        if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
                            // Enviar el formulario una vez que todos los archivos han sido cargados o fallaron
                            $('form')[0].submit();
                        }
                    });
                    uploader.start();
                } else {
                    alert('Debe cargar al menos un archivo.');
                }
                return false;
            });
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <h2>Carga Múltiple de Archivos</h2>
        </div>
        <div id="uploader" style="width: 600px">
            <p>Su navegador no soporta Flash, Silverlight, Gears, BrowserPlus o HTML5.</p>
        </div>
    </form>
</body>
</html>

Resultado de la Interfaz:Interfaz de carga

**Archivo uploadFiles.ashx (Back end - ASP.NET Handler):**Este archivo maneja las solicitudes de carga del lado del servidor.

using System;
using System.IO;
using System.Web;

public class uploadFiles : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        HandleFileUpload(context);
    }

    public bool IsReusable
    {
        get { return false; }
    }

    private void HandleFileUpload(HttpContext context)
    {
        context.Response.CacheControl = "no-cache";
        string uploadDirectory = FileHelper.GetUploadPath(); // Directorio base para las cargas

        string dateSubDir = DateTime.Now.ToString("yy-MM-dd");
        string fullUploadDir = Path.Combine(uploadDirectory, dateSubDir);

        if (!Directory.Exists(fullUploadDir))
        {
            Directory.CreateDirectory(fullUploadDir);
        }

        try
        {
            int chunkIndex = Convert.ToInt32(context.Request["chunk"]); // Índice del fragmento actual
            int totalChunks = Convert.ToInt32(context.Request["chunks"]); // Número total de fragmentos
            string originalFileName = context.Request["name"]; // Nombre original del archivo

            // Si el archivo no se divide en fragmentos (totalChunks == 1)
            if (totalChunks == 1)
            {
                HttpPostedFile file = context.Request.Files[0]; // Obtener el archivo
                if (file.ContentLength > 0)
                {
                    string filePath = Path.Combine(fullUploadDir, originalFileName);
                    file.SaveAs(filePath);
                }
            }
            else // El archivo se está cargando en fragmentos
            {
                string tempFilePath = SaveTemporaryChunk(context.Request.Files[0], originalFileName, chunkIndex);

                // Si es el último fragmento, ensamblar el archivo completo
                if (chunkIndex == totalChunks - 1)
                {
                    string finalFilePath = Path.Combine(fullUploadDir, originalFileName);
                    FileInfo tempFile = new FileInfo(tempFilePath);

                    // Si ya existe un archivo con el mismo nombre, eliminarlo
                    if (File.Exists(finalFilePath))
                    {
                        File.Delete(finalFilePath);
                    }
                    tempFile.MoveTo(finalFilePath); // Mover el archivo temporal a la ubicación final
                }
            }
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = 500; // Indicar error del servidor
            context.Response.Write($"Error al procesar la carga: {ex.Message}");
        }
    }

    /// <summary>
    /// Guarda un fragmento temporal del archivo.
    /// </summary>
    private string SaveTemporaryChunk(HttpPostedFile uploadedFile, string originalFileName, int chunkNumber)
    {
        string tempDir = FileHelper.GetTempPath(); // Directorio temporal
        string chunkFileName = $"{originalFileName}.part{chunkNumber}";
        string tempFilePath = Path.Combine(tempDir, chunkFileName);

        // Crear el archivo temporal si no existe o añadir el fragmento si ya existe
        using (FileStream fs = new FileStream(tempFilePath, chunkNumber == 0 ? FileMode.Create : FileMode.Append))
        {
            if (uploadedFile.ContentLength > 0)
            {
                byte[] buffer = new byte[uploadedFile.ContentLength];
                uploadedFile.InputStream.Read(buffer, 0, uploadedFile.ContentLength);
                fs.Write(buffer, 0, buffer.Length);
            }
        }

        // Si es el último fragmento, renombrar el archivo temporal para que coincida con el nombre original
        if (Convert.ToInt32(HttpContext.Current.Request["chunks"]) - 1 == chunkNumber)
        {
            string reassembledFilePath = Path.Combine(tempDir, originalFileName);
            if (File.Exists(reassembledFilePath))
            {
                File.Delete(reassembledFilePath);
            }
            File.Move(tempFilePath, reassembledFilePath);
            return reassembledFilePath;
        }

        return tempFilePath;
    }
}

// Asumiendo que FileHelper es una clase auxiliar definida en otro archivo
public static class FileHelper
{
    public static string GetUploadPath()
    {
        string basePath = HttpContext.Current.Server.MapPath("~/");
        string subDir = ConfigurationManager.AppSettings["uploaddir"] ?? "Uploads";
        string uploadDir = Path.Combine(basePath, subDir);
        CreateDirectoryIfNotExists(uploadDir);
        return uploadDir;
    }

    public static string GetTempPath()
    {
        string basePath = HttpContext.Current.Server.MapPath("~/");
        string subDir = ConfigurationManager.AppSettings["tempdir"] ?? "TempUploads";
        string tempDir = Path.Combine(basePath, subDir);
        CreateDirectoryIfNotExists(tempDir);
        return tempDir;
    }

    private static void CreateDirectoryIfNotExists(string path)
    {
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
    }
}

**Paso 4: Clase Auxiliar FileHelper**Esta clase proporciona utilidades para gestionar directorios de carga y temporales.

using System;
using System.IO;
using System.Web;
using System.Configuration; // Necesario para ConfigurationManager

public static class FileHelper
{
    /// <summary>
    /// Obtiene la ruta base para las cargas, basada en la configuración.
    /// </summary>
    public static string GetUploadPath()
    {
        string basePath = HttpContext.Current.Server.MapPath("~/");
        // Lee el nombre del subdirectorio de uploads desde el web.config
        string subDirName = ConfigurationManager.AppSettings["uploaddir"] ?? "UploadedFiles";
        string fullPath = Path.Combine(basePath, subDirName);
        CreateDirectoryIfNotExists(fullPath);
        return fullPath;
    }

    /// <summary>
    /// Obtiene la ruta para los archivos temporales, basada en la configuración.
    /// </summary>
    public static string GetTempPath()
    {
        string basePath = HttpContext.Current.Server.MapPath("~/");
        // Lee el nombre del subdirectorio de temporales desde el web.config
        string tempDirName = ConfigurationManager.AppSettings["tempdir"] ?? "TempFiles";
        string fullPath = Path.Combine(basePath, tempDirName);
        CreateDirectoryIfNotExists(fullPath);
        return fullPath;
    }

    /// <summary>
    /// Crea un directorio si no existe.
    /// </summary>
    private static void CreateDirectoryIfNotExists(string path)
    {
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
    }
}

Asegúrese de tener configurados los appSettings en su web.config para uploaddir y tempdir si desea personalizar los nombres de los directorios.

Paso 5: Miniaturas de ImágenesSi la opción resize está activada en la configuración de Plupload, las imágenes se redimensionarán en el cliente antes de la carga.

  • Con resize activado: Imagen con miniatura
  • Con resize comentado: Si se comenta la línea resize: { width: 320, height: 240, quality: 90 },, Plupload no generará miniaturas en el cliente. Imagen sin miniatura

Paso 6: Localización al EspañolPara mostrar la interfaz en español, incluya el archivo de idioma correspondiente.

<!-- ... otros scripts y estilos ... -->
<script type="text/javascript" src="Scripts/plupload.full.js"></script>
<script type="text/javascript" src="Scripts/i18n/es.js"></script> <!-- Archivo de idioma español -->
<script type="text/javascript" src="Scripts/jquery.ui.plupload/jquery.ui.plupload.js"></script>
<!-- ... -->

Interfaz en Español:Interfaz en español

**Mensajes de Error:**Los mensajes de error se mostrarán según la configuración del idioma. Mensajes de error

**Archivos de Ejemplo:**Se proporcionan dos archivos de ejemplo:

  • WebUploadFileTest2.aspx: Corresponde al código de ejemplo principal de este tutorial.
  • WebUploadFileTest.aspx: Otro archivo de ejemplo para demostración.

Etiquetas: plupload carga de archivos carga masiva JavaScript jQuery

Publicado el 7-26 03:00