Cómo subir un archivo a tu servidor web mediante el control FileUpload de ASP.NET

jueves 6 de diciembre de 2007

Este es un tip súper sencillo, pero bastante útil, y como ya van varias personas que me lo preguntan, mejor lo explico aquí.

A menudo se ofrece que los usuarios de nuestra aplicación web nos envíen (o "suban") algún archivo al servidor web donde corre nuestra aplicación ASP.NET. Pudiera ser que el gerentoide tiene un archivo de texto o .CSV que necesitamos leer para cargarlo en la base de datos, o una imágen que vamos a poner como avatar en el perfil del usuario, qué se yo.

Con ASP.NET 2.0 o posterior, subir un archivo a tu servidor es sencillísimo mediante el control FileUpload. Supongamos que tenemos una forma de ASP.NET como esta:

Una forma ASP.NET para subir un archivo a tu servidor web

El markup sería el siguiente:

<%@ Page Language="C#" AutoEventWireup="true" 
   CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
   <title>Subir un archivo</title>
</head>
<body>
   <form id="form1" runat="server">
        <div>
            <h3>
               Cómo subir un archivo a tu servidor web mediante ASP.NET</h3>
            <p>
               Elige el archivo a subir:
               <asp:FileUpload ID="trepador" runat="server" />
            </p>
            <p>
               <asp:Button ID="subir" runat="server" 
                    Text="Subir Archivo" OnClick="subir_Click" />
            </p>
            <p>
               <asp:Label ID="estatus" runat="server" 
                    Style="color: #0000FF"></asp:Label>
            </p>
        </div>
   </form>
</body>
</html>

Como puedes ver la forma en realidad consiste de únicamente de un control FileUpload, un botón para iniciar el postback, y un Label que usaré para mostrar un mensaje. Al botón le asociamos un manejador para el evento Click—que veremos un poquito más abajo.

Las propiedades y métodos relevantes del control FileUpload son:

  • SaveAs(), que se usa para decir dónde quieres guardar el archivo que se está subiendo. Debes proporcionar la ruta completa y el nombre.
  • HasFile, que indica si el usuario ya escribió el nombre de un archivo o lo seleccionó mediante el botón "Browse..."

Ahora, este es el código para manejar el evento:

using System;
using System.IO;
 
public partial class _Default : System.Web.UI.Page
{
   protected void subir_Click(object sender, EventArgs e)
   {
      if (trepador.HasFile)
      {
         string directorio = @"C:\Archivos de Usuario\";
 
         if (Directory.Exists(directorio))
         {
            string archivo = directorio + trepador.FileName;
 
            if (File.Exists(archivo))
            {
               // ya existe un archivo con el mismo nombre en el directorio,
               // así que hay hacer algo al respecto (p.ej. renombrar el que 
               // está en el servidor o asignarle otro nombre al que se está 
               // subiendo), de lo contrario el archivo en el servidor será 
               // sobreescrito
            }
            else
            {
               trepador.SaveAs(archivo);
               estatus.Text = "Tu archivo ha sido enviado exitosamente.";
 
               // 
               // TODO: código para procesar el archivo va aquí...
               //
            }
         }
         else
         {
            throw new DirectoryNotFoundException(
               "El directorio en el servidor donde se suben los archivos no existe");
         }
      }
   }
}

Consideraciones adicionales

Hay dos aspectos más que debes cuidar con esto de la "trepada" de archivos.

Primero, debes asegurarte que la cuenta de usuario bajo la que corre el ASP.NET Worker Process (típicamente la cuenta se llama ASPNET) tenga suficientes permisos de seguridad sobre el directorio donde quieres escribir los archivos en el servidor, de lo contrario verás una excepción de seguridad. Creo que el permiso mínimo que necesita es de Write, aunque yo casi siempre uso el de Modify en mi máquina local.

 Permisos necesarios para que ASP.NET pueda subir (escribir) archivos en el servidor

Y segundo, ASP.NET, por default, está configurado para subir archivos de hasta 4 Mb únicamente (4096 bytes). Esto es porque la petición la recibe IIS primero y debe "tragársela" completa antes de pasársela al Worker Process. Si lo que vas a subir son archivos de texto, pues 4 Mb es un chorro, pero si es otro tipo de archivos—imágenes p0rno, archivos de Word, etcétera—pues esto podría ser terriblemente insuficiente. Si el archivo es muy grande, el otro problema es que la petición de HTTP pudiera exceder el tiempo máximo por petición y hacer timeout. Afortunadamente podemos alterar esto mediante el archivo de configuración del sitio (web.config):

<?xml version="1.0"?>
<configuration>
   <system.web>
      <!--  Requerido para subir archivos grandes. En este caso 
         especificas un tiempo máximo de 10 minutos (600 segundos)
         y un tamaño máximo de archivos de 50Mb
   -->
      <httpRuntime executionTimeout="600" maxRequestLength="51200"/>
   </system.web>
</configuration>

En la configuración anterior exageré un bastante, y deberías tener cuidado de valores como estos, ya que estás indicando que puede haber peticiones de ASP.NET que duren hasta 10 minutos ejecutándose, y esto podría llevar a que tu servidor se sature si de pronto hay varias peticiones grandes. Para la mayoría de las aplicaciones el valor default de 110 segundos (minuto y medio) como executionTimeout es suficiente. Especificar valores grandes para el maxRequestLength, de manera similar, expone tu servidor a ser saturado, así que debes alterar estos valores solo si en verdad lo requieres, y solo para los valores máximos que se necesiten para tu aplicación.

Por último, la documentación de ASP.NET dice que se debe especificar el valor de executionTimeout debe especificarse como un TimeSpan (p.ej. "00:01:50"), pero en la práctica, no he podido utilizarlo así. Siempre he tenido que especificarlo como el número de segundos.

Conclusión

El control FileUpload hace que el problema de subir achivos a tu servidor web sea pan comido. Solo necesitas agregarlo a tu página ASPX y agregar un botón u algún otro control para que invoque la lógica necesaria para copiar el archivo a tu servidor mediante el método SaveAs(). Solo debes cuidar que la cuenta ASPNET tenga los permisos adecuados, y que la configuración del servidor sea apropiada para los tipos de archivos que vayas a subir.

Hay muchas mejoras que se le puede hacer al ejemplo de este artículo. La primera que se me ocurre a mi es proporcionar algún tipo de retroalimentación al usuario de que el archivo se está procesando, o deshabilitar el botón inmediatamente después del click para que el usuario no le de click dos veces hasta que termine de subirse el archivo—estos son problemas bastante comunes cuando quieres subir archivos grandes y ya sea que tu servidor esté algo ocupado o tu usuario sea un neurótico desesperado que no puede esperar ni los 7 segundos promedio como el resto de los seres humanos normales... smile_baringteeth smile_wink pero en fin, esa ya es harina de otro costal, y tendría que hablar de ASP.NET AJAX. Quizá en la próxima.

Enjoy. smile_shades

categorías: , , ,

11 comentarios:

  1. BlackTigerX dijo:

    tambien se debe ser bastante cuidadoso con la seguridad, si sabemos que tipo de archivos pueden subir, debemos checar las extensiones de los archivos y no permitir nada que contenga otras extensiones

    ademas, generalmente deberiamos subir los archivos a una ruta que no este expuesta a travez del servidor web, esto para evitar ejecucion remota de codigo

    salu2

  2. KaMiKaZe aka Carlos dijo:

    Muy cierto y ¡buena observación! La manera más sencilla que se me ocurre de hacerlo es a través de un RegularExpressionValidator aplicado al FileUpload que el archivo tenga una extensión de las "aprobadas". Aunque también podrías hacerlo en el code-behind.

  3. SnoopDogg dijo:

    Hola! Que tal? Googleando un poco en busca de mi error, he encontrado su página hablando sobre el control FileUpload, y tengo un problema con ese control, a ver si me pueden ayudar.

    El error es el siguiente:
    - Al adjuntar un fichero superior a 4MB, que es el limite por defecto del control FileUpload, al realizar cualquier tipo de operación, la página da error, y no hay ninguna forma de controlarlo. Lo que yo quisiera es poder mostrarle una pagina de error "amigable" al usuario diciendole que el limite está en 4 MB, y que por eso ha fallado.

    Tengo declarada una seccion de error para manejar esto, pero no me hace caso:

    Protected Sub Page_Error(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Error
    Me.ErrorPage = "error.aspx"
    Dim ex As System.Web.HttpException
    ex = Server.GetLastError()
    If ex.ErrorCode = -2147467259 Then
    Server.ClearError()
    Response.Redirect("error.aspx")
    End if


    GraCIAS!

  4. KaMiKaZe aka Carlos dijo:

    Qué tal SnoopDog,
    El ErrorCode, ¿no debe ser algo así como 400?

    Si eso no funciona, lo que he hecho en el pasado es implementar un HttpModule que revise el Request.ContentLength y si excede eso redireccionar la página o levantar un error. Esto es un poco más avanzado y no creo poder explicarlo en un simple comentario, pero puedo escribir un artículo al respecto. Solo dime si te interesa :)

  5. Norwill A Gutiérrez F dijo:

    Hola que tal saludos desde venezuela, este post y el de como leer archivos planos con ado.net me han servido de mucho, ahora una cuestion, sera posible que yo procese ese archivo una vez estando en un servidor web con algo asi como lo que explicas en el pos de archivos planos con ado.net??? y tambien queria saber si existe alguna manera de en asp.net cree otro archivo de texto a partir del que subo y luego el usuario lo descargue o lo abra ?? gracias

  6. Carlos Rubalcava dijo:

    Por supuesto que sí. Si te fijas, en el código de este artículo, despues del SaveAs() hay una línea que dice:

    // TODO: código para procesar el archivo va aquí...

    En este lugar podrías hacer la llamada a la rutina de LeerArchivoPlano() del artículo de "cómo leer archivos planos..." para leer el archivo que acabas de subir. Algo así como:

    DataTable miDataTable = LeerArchivoPlano(new FileInfo(archivo), TipoDeArchivoPlano.Delimited);

    Y ya tendrías un DataTable con los datos del archivo que se acaba de subir (asumiendo claro, que el archivo esté bien formado de acuerdo a tus especificaciones).

    Ya de ahí lo que hagas con tus datos depende de ti. Si generas otro archivo y quieres que el usuario lo descargue tienes de varias sopas. Podrías generarlo en el servidor y luego ponerle la ubicación como el URL de un control tipo Hyperlink (o usar un vulgar tag de <A HREF=""> de HTML) o podrías usar Response.Redirect() para "redirigir" al usuario al archivo nuevo (para algunos browsers como IE, el efecto de redireccionarlos a una archivo con extensión .CSV típicamente es que les sale la cajita de diálogo de "Save As...", pero igual y tendrías que configurar el mime type y esas cosas para obtener el resultado deseado.

    Espero te sirva.

  7. Cecilia dijo:

    Hola!!!
    El ejemplo esta excelente, solo que quisiera saber como seria para que en lugar de que se fuera a un directorio de "C:", guardar el archivo en un campo de base de datos.

    Gracias!!

  8. Carlos Rubalcava dijo:

    Cecilia--

    íjole, ahí sí nos tendríamos que salir del tema poquito, porque habría que hablar bastante de ADO.NET y cómo maneja columnas binarias...

    A grandes razgos es así: tendrías que tener una columna en tu BD para almacenar los datos binarios. En SQL Server 2005 el tipo de dato para la columna es varbinary. Luego tendrías que definir lo necesario en tu DataTable y tu DataAdapter o TableAdapter para insertar ese tipo de dato.

    Una vez que ya tienes eso, puedes usa la propiedad FileBytes del control FileUpload y pasar el stream de bytes a tu adaptador.

    La verdad no sé cómo explicarlo así en una respuesta corta... más bien creo que me acabas de dar el tema para mi siguiente artículo :)

  9. PrOcu dijo:

    Hola!

    Oigan necesito de su valiosa ayuda. Uds saben cómo puedo hacerle para poder subir un archivo desde una PDA hacia el servidor, lo que pasa es que el mobile web form no acepta el input file ni el input submit ni el enctype que en web forms si son aceptados..

    Agradezco su ayuda de antemano

  10. Carlos Rubalcava dijo:

    PrOcu--
    La verdad no tengo ni idea. Mi experiencia con dispositivos móviles es muy limitada, pero quizá alguien en la Comunidad sepa. Podrías dejar un mensaje en el foro.

  11. Glei dijo:

    Hola, en busca de darle solucion a mi problema me encontre con este forum. Tengo el mismo problema deSnoopDogg. Gracias de antemano si me pueden ayudar.

    El error es el siguiente:
    - Al adjuntar un fichero superior a 4MB, que es el limite por defecto del control FileUpload, al realizar cualquier tipo de operación, la página da error, y no hay ninguna forma de controlarlo. Lo que yo quisiera es poder mostrarle una pagina de error "amigable" al usuario diciendole que el limite está en 4 MB, y que por eso ha fallado.

    Tengo declarada una seccion de error para manejar esto, pero no me hace caso: