ASP.NET: Subir archivos al servidor/base de datos

Importante: Junto con este artículo y el siguiente (Redimensionar imágenes), recomiendo leer un artículo más nuevo: Redimensionar y guardar imágenes en ASP.NET, con el cual se resuelven algunos detalles de este.

A veces, es necesario permitirle al usuario cargar archivos desde su computadora, como imágenes, documentos, o archivos de cualquier tipo.

Por ejemplo, si estamos haciendo un sistema para una tienda, podemos permitirle al usuario cargar fotografías de los productos cuando los registra en el catálogo.

Lo que el usuario sube lo podemos guardar de dos formas: directamente en una carpeta de nuestra aplicación web ó en una tabla de una base de datos.

Comenzaré creando el markup de la página:

  <form id="form1" runat="server">
    <div>
    <div>
      <asp:RadioButton id="disco" runat="server" GroupName="guardar"
	    Checked="true" Text="Guardar imagen en disco"/>
      <asp:RadioButton id="bd" runat="server" GroupName="guardar"
	    Text="Guardar imagen en base de datos"/>
    </div>
    <p>
      <asp:Label AssociatedControlId="fileUploader1" runat="server"
	    Text="Seleccionar una imagen:" />
      <asp:FileUpload id="fileUploader1" runat="server" />
    </p>
    <asp:Button id="cargarImagen" runat="server"
	  Text="Cargar imágenes" OnClick="cargarImagen_Click"/>
    </div>
  </form>
disco
Guardar archivo en una carpeta de la aplicación web.
bd
Guardar archivo en la base de datos.
fileUploader1
Objeto input de tipo file para cargar el archivo.
cargarImagen
Botón que procesa el archivo y lo guarda según la opción seleccionada.

El código para el botón cargarImagen sería el siguiente:

protected void cargarImagenes_Click(
  object sender, EventArgs e) {
  try {
    if (fileUploader1.HasFile) {
      // Se verifica que la extensión sea de un formato válido
      string ext = fileUploader1.PostedFile.FileName;
      ext = ext.Substring(ext.LastIndexOf(".") + 1).ToLower();
      string[] formatos = 
        new string[] { "jpg", "jpeg", "bmp", "png", "gif" };
      if (Array.IndexOf(formatos, ext) < 0)
        MensajeError("Formato de imagen inválido.");
      else if (disco.Checked)
        GuardarArchivo(fileUploader1.PostedFile);
      else
        GuardarBD(fileUploader1.PostedFile);
    }
    else
      MensajeError("Seleccione un archivo del disco duro.");
  }
  catch (Exception ex) {
    MensajeError(ex.Message);
  }
}

Primero, se verifica que se haya seleccionado un archivo en el control fileUpload1, checando su propiedad HasFile. Después, se revisa la extensión del archivo (ya que estamos limitando la carga a imágenes solamente). Finalmente, dependiendo la opción que se seleccionó desde la página, invocaremos a un método para guardar el archivo en el disco duro o en la base de datos. En ambos métodos estamos enviando como parámetro un objeto HttpPostedFile contenido en el control FileUpload.

Guardar archivos en el disco duro

Guardar un archivo desde un control FileUpload no tiene la menor complejidad. El mismo objeto HttpPostedFile tiene un método para guardar en el disco duro, llamado (oh, sorpresa!) SaveAs.

private void GuardarArchivo(HttpPostedFile file) {
  // Se carga la ruta física de la carpeta temp del sitio
  string ruta = Server.MapPath("~/temp");

  // Si el directorio no existe, crearlo
  if (!Directory.Exists(ruta))
    Directory.CreateDirectory(ruta);

  string archivo = String.Format("{0}\\{1}", ruta, file.FileName);

  // Verificar que el archivo no exista
  if (File.Exists(archivo))
    MensajeError(String.Format(
      "Ya existe una imagen con nombre\"{0}\".", file.FileName));
  else {
    file.SaveAs(archivo);
  }
}

Guardar archivos en la base de datos

Una imagen, como cualquier archivo de nuestra computadora, está formada por un conjunto de bytes. En SQL, existen los tipos de datos BLOB (binary large object), usados para guardar esta clase de datos (archivos). En T-SQL de SQL Server, tenemos los tipos de dato IMAGE y BINARY, que nos permiten guardar archivos en la base de datos.

Nuestra imagen, contenida en el objeto HttpPostedFile dentro de un objeto de clase FileStream, deberá ser convertida a un conjunto de bytes para enviarlo a la base de datos. Dicho de otro modo, convertiremos el objeto de tipo Stream a un arreglo de bytes.

También estoy guardando la extensión y el tipo de contenido (contentType) de la imagen, ya que estos datos muy probablemente me sirvan al momento de descargar el archivo de la base de datos, principalmente si trato con archivos de distinto tipo.

private static void GuardarBD(HttpPostedFile file) {
  // Nombre de la imagen
  string nombre = file.FileName.Substring(
    0, file.FileName.LastIndexOf("."));
  // Extensión del archivo
  string ext = nombre.Substring(nombre.LastIndexOf(".") + 1);
  // Tipo de contenido
  string contentType = file.ContentType;
  // Imagen convertida a arreglo de bytes
  byte[] imagen = new byte[file.InputStream.Length];
  file.InputStream.Read(imagen, 0, imagen.Length);

  // Se insertan los valores en la base de datos
  SqlConnection cnx = GetConnection();
  try {
    cnx.Open();
    SqlCommand cmd = cnx.CreateCommand();
    cmd.CommandText =
      "INSERT INTO Imagenes (nombre, imagen, extension, contentType) " +
      "VALUES (@nombre, @imagen, @ext, @contentType)";
    cmd.Parameters.AddWithValue("@nombre", nombre);
    cmd.Parameters.AddWithValue("@imagen", imagen);
    cmd.Parameters.AddWithValue("@ext", ext);
    cmd.Parameters.AddWithValue("@contentType", contentType);
    cmd.ExecuteNonQuery();
  }
  catch (Exception ex) {
    throw ex;
  }
  finally {
    if (cnx != null) {
      if (cnx.State == ConnectionState.Open)
        cnx.Close();
      cnx.Dispose();
    }
  }
}

Al final, no es tan complicado crear un sistema de carga de archivos para el usuario.

A mi punto de vista, es mejor guardar las imágenes en una carpeta que subirlas a la base de datos. Sería más carga para el servidor de BD cargar ahí las imágenes, además de que se ocupa más procesamiento para descargarlas y nuevamente convertirlas a imagen. Es mejor guardar en el disco duro la imagen y en la base de datos, la ruta a dicha imagen.

Otra cosa, referente a imágenes: es muy recomendable redimensionarlas. Siguiendo con el ejemplo del catálogo de productos, si el usuario le tomó una fotografía al producto con su super cámara de 12 megapixeles y calidad ultra-mega-chido-cool, será muy pesado subirla al servidor y también demasiado molesto para el usuario descargar una imagen tan grande, solo para ver el producto. Será mejor ajustar la imagen a un tamaño estándar. Hablaré de esto en otra ocasión.

19 comentarios sobre “ASP.NET: Subir archivos al servidor/base de datos

  1. Hola, muy buena tu expliccion.
    Pero se me presenta un problema al momento de leer el fileupload, al llegar a mi validacion me dice queno tiene ningun archivo asociado.

    Me podrias ayudar como resovler esto??

    Saludos

  2. hola buen dia

    la verdad excelente es una excelente explicacion, funciona muy bien solo hice algunos ajustes para que me funcionara en el proyecto que estoy trabajando, slo tengo una duda como recuperaria los archivos de manera inversa, por ejemplo los que se guardan en la bd o en el disco del servidor, te agradeceria mucho tu apoyo

    de antemano muchas gracias

      1. Buen dia una pregunta como guardo un archivo pdf estoy trabajando con asp.net vb 2010 y mysql.. ya subo archivos a una carpeta temporal pero no se como guadarlos en la base de datos..

        mi formulario tienes varios campos y una opcion para guardar el pdf .
        si me pudiera ayudar por favor.

      2. En tu base de datos necesitas un campo de tipo binario (binary o varbinary). El arreglo de bytes que generas desde la aplicación lo tienes que mandar a la base de datos como parámetro del mismo tipo.

        SqlCommand cmd = connection.CreateCommand();
        cmd.CommandText = «INSERT INTO … VALUES(@archivo, ….)»;
        cmd.Parameters.AddWithValue(«@archivo», arreglo_bytes);

  3. Buen Post, estoy tratando de implementarlo sin embargo me da errores, si fueras tan amable de enviarme el ejemplo que ya funciona. Gracias de antemano.

  4. Tengo una duda. En caso de una modificación ¿Cómo se hace la carga de la imagen desde la ruta del servidor a el input type=»file»?.

    1. El input de tipo file solo puede tener la imagen que el usuario va a enviar al servidor. Lo que puedes hacer es mostrar la imagen en una etiqueta img. Si el usuario quiere modificarla, usar el input para cargar una nueva. Pero no puedes poner la imagen que está en el servidor dentro del input.

  5. La problematica esta muy bien definida …. pero creo que tambien analizar lo de la forma web …. cono nota si estan creando una pliacion web esta parte les ayudara un poco … pero asta el mometo de imprimir los msjs no se puede y marca error aqui —–>

    catch (Exception ex)
    {
    MessageBox.Show(ex.ToString());
    }

    yo recomiendo utilizar el ——>

    catch (Exception e)
    {
    throw new Exception(e.Message);
    }

Deja un comentario