miércoles, 14 de marzo de 2012

MultiUpload dinámico



Hay ocasiones en las que no sabemos de forma exacta cuántos archivos va a cargar el usuario a nuestra aplicación web, por ejemplo una galería fotográfica, donde le damos la posibilidad de cargar 1 o más archivos.

El dolor de cabeza radica en "1 o más" dado que lo que menos queremos es que el usuario tenga que estar dando clic en un botón "Agregar" para que se haga un POST al servidor y generemos otro control de tipo FileUpload y así pueda cargar más fotografías.

En esta entrada explicaré de una forma sencilla y sumamente básica cómo podemos generar controles FileUpload del lado del cliente y así evitemos dar viajes sin sentido al servidor y por lo tanto mejorar la experiencia de nuestro usuario.

La herramienta más sencilla (y versátil) que he encontrado para poder modificar la página desde el navegador del cliente es jquery, por lo que agregaremos el siguiente elemento script en el head de nuestra página Default.aspx (suponiendo que ya cuentan con él y lo han colocado en la carpeta js de nuestro proyecto Web):

<script language="javascript" type="text/javascript" src="js/jquery.js"></script>

Y colocaremos la siguiente interfaz:

<table>
    <tr>
        <td colspan="2"><input type="button" value="Add" onclick="return addUploadField()" /></td>
    </tr>
    <tr>
        <td><input type="file" name="file0" /></td>
        <td></td>
    </tr>
    <tr id="trUploadRow">
        <td colspan="2" align="right"><asp:Button ID="cmdUpload" runat="server" Text="Upload" OnClick="cmdUpload_Click" /></td>
    </tr>
</table>

Tenemos un botón "Add" que nos servirá para agregar un nuevo control de carga de archivos, en las filas siguientes se agregarán los controles de carga de archivos junto con un botón para eliminar el control (si es que el usuario ya no quiere cargar ese archivo) y un botón ASP .NET que ejecutará el POST de la página para la carga de los archivos.

Notemos que es muy importante que nuestros controles de carga de archivos (input de tipo file) deben de contar con el atributo "name" para que éstos sean reconocidos por la propiedad Files del objeto Request.

Ahora escribiremos la función javascript addUploadField que será la encargada de crear los controles de carga:

<script language="javascript" type="text/javascript">
    var currentIndex = 0;
    function addUploadField() {
        currentIndex++;
        var newRow = '<tr>';
        newRow += '<td><input type="file" name="file' + currentIndex + '" /></td>';
        newRow += '<td><input type="button" value="Remove" onclick="return removeUploadField(this)" /></td>';
        newRow += '</tr>';
        newRow = $(newRow);
        newRow.insertBefore($('#trUploadRow'));
    }
    function removeUploadField(e) {
        $(e.parentNode.parentNode).remove();
    }
</script>

La variable currentIndex me ayudará a darle un nombre único a cada control que se agregue.   En la función addUploadField estoy declarando una variable newRow donde estaré concatenando el código HTML necesario para crear el control de carga y el botón de eliminar.

Al atributo name le estoy asignando un valor único al concatenar la variable currentIndex que se incrementa cada vez que el usuario agregue un nuevo control.

En el botón "Remove" estoy llamando a una función "removeUploadField" que será la encargada de eliminar la fila.

Una vez que tengo el código HTML necesario para representar a mi control de carga y mi botón de eliminar, convertiremos el código en objetos jquery para poder manipularos de una forma mucho más simple.    Lo interesante del objeto jquery es que utilizando su funcion insertBefore lo podemos colocar justo arriba del botón "Upload".

En la función removeUploadField únicamente recibimos como parámetro el botón que está llamando a la función, y dado que HTML es un árbol de nodos, podemos obtener a través de la propiedad "parentNode" al objeto TR que queremos eliminar.

Ahora vayamos al archivo Default.aspx.cs donde escribiremos el código de nuestro manejador del evento Click del botón Upload:

using System.IO;
protected void cmdUpload_Click(object sender, EventArgs e)
{
    foreach (string file in Request.Files)
    {
        if (Request.Files[file].ContentLength > 0)
        {
            if (File.Exists(Server.MapPath("~/files/" + Request.Files[file].FileName)))
                File.Delete(Server.MapPath("~/files/" + Request.Files[file].FileName));
            Request.Files[file].SaveAs(Server.MapPath(("~/files/" + Request.Files[file].FileName)));
        }
    }
}

Si ejecutamos la aplicación veremos que aún cuando la estructura de los objetos necesarios para llevar a cabo la carga de los archivos es la adecuada, éstos no son recibidos de forma correcta.    Pongamos un punto de interrupción en la línea del "foreach" y exploremos la propiedad "ContentType" del objeto Request, veremos que tiene el valor "application/x-www-form-urlencoded" y para que los archivos sean transferidos de forma correcta el ContentType debe ser "multipart/form-data".

Se me ocurren dos maneras de corregir este problema:

1. Modificando la etiqueta form de Default.aspx agregándole el atributo "enctype" con el valor "multipart/form-data" de manera tal que quede así:
<form id="form1" runat="server" enctype="multipart/form-data">

2. En el procedimiento Page_Load verificar el valor de la propiedad ContentType del objeto Request y corregirlo si es necesario:

protected void Page_Load(object sender, EventArgs e)
{
    if (form1.Enctype != "multipart/form-data")
        form1.Enctype = "multipart/form-data";
}

Ocupen la que gusten, las dos opciones resultan en la carga correcta de archivos.

Espero haya sido de mucha utilidad esta entrada y les ayude a arrancar con controles propios y extender su funcionalidad tanto como ustedes gusten.

No hay comentarios.:

Publicar un comentario