Introducción
Este articulo tendrá por objetivo demostrar algunas técnicas de tratamiento de archivos cuando es necesario manipularlos para que el cliente desde una página web tenga acceso a ellos.
Entre las operaciones no solo se permitirá la descarga sino que además el cliente podrá subir documentos, y guardar estos en una base de datos.
Listar archivos en una carpeta del sitio
Para este trabajo se hará uso del control GridView el cual listara los archivos contenidos en la carpeta del sitio.
Debe remarcarse que siempre que se trabaje con archivos que son subidos o trabajados en el sitio deberá usarse carpetas que estén dentro de la estructura de dicho sitio, no es una buena practica usar rutas como ser c:\, o D:\temp, si el sitio no ha sido creado allí.
public partial class ListarImagenes : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DirectoryInfo dirInfo = new DirectoryInfo(Server.MapPath("~/files"));
FileInfo[] fileInfo = dirInfo.GetFiles("*.*", SearchOption.AllDirectories);
GridView1.DataSource = fileInfo;
GridView1.DataBind();
}
}
protected void btnSubirArchivo_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
string fullPath = Path.Combine(Server.MapPath("~/files"), FileUpload1.FileName);
FileUpload1.SaveAs(fullPath);
}
}
}
En el evento Page_Load puede observarse como se hace uso de la funcionalidad de System.IO para recuperar el listado de archivos que se encuentran en la carpeta de nombre “files”.
En este caso para poder recuperar el path físico se hace uso de Server.MapPath(), y se referencia la ruta mediante ~/ ya que al encontrarse la carpeta en un nivel distinto es necesario hace referencia desde el root del sitio, en este caso usar ../file también hubiera funcionado.
Al ejecutar la pagina ~/ListarArchivos/ListarImagenes.aspx, se visualizara un resultado como el siguiente:

La grilla toma los valores de las propiedades del objeto FileInfo, es por eso que se hace uso de nombres como Name o Length para mostrar la información de la tabla.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4"
ForeColor="#333333" GridLines="None">
<RowStyle BackColor="#EFF3FB" />
<Columns>
<asp:TemplateField HeaderText="Nombre Archivo">
<ItemTemplate>
<asp:HyperLink ID="nombre" runat="server" NavigateUrl='<%# Eval("Name", "~/files/{0}") %>'
Text='<%# Eval("Name") %>'>
</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Length" HeaderText="Tamaño" />
<asp:TemplateField HeaderText="Descargar">
<ItemTemplate>
<asp:HyperLink ID="descarga" runat="server" NavigateUrl='<%# Eval("Name", "~/ListarArchivos/Download.aspx?filename={0}") %>'>
<img src="../imagenes/download.gif" alt="" width="30px" height="30px" style="border-width:0px;" />
</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<EditRowStyle BackColor="#2461BF" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
Además debe diferenciarse entre los dos link visualizados en la grilla, ya que estos trabajan de distintas formas,
-el primero es un link ordinario que apunta directo al archivos en la carpeta,
-el segundo es un link que fuerza la descarga del archivo a la PC del usuario, para su pre-visualización o guardar en disco.
Es interesante analizar el segundo caso, para este se hace uso de una pagina adicional que recibe como parámetro por QueryString el nombre del archivos.
Esta pagina buscara este valor pasado por parámetro en la carpeta del sitio donde se alojan las imágenes, y mediante el uso del objeto Response pondrá el archivo como respuesta del cliente.
La pagina que realiza esta tareas, es ~/ListarArchivos/Download.aspx :
public partial class Download : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string filename = Request.QueryString["filename"].ToString();
Response.Clear();
Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", filename));
switch (Path.GetExtension(filename).ToLower())
{
case ".jpg":
Response.ContentType = "image/jpg";
break;
case ".gif":
Response.ContentType = "image/gif";
break;
case ".png":
Response.ContentType = "image/png";
break;
}
Response.WriteFile(Server.MapPath(Path.Combine("~/files", filename)));
Response.End();
}
}
De esta pagina se detecta al tipo de archivo mediante la extensión, especificando el ContentType correcto, debe remarcarse que esta técnica no solo funcionaria con documentos de imágenes, si el en futuro es necesario adaptarlo para la descarga de otro tipo de archivos, solo será cuestión de agregar el soporte a nuevas extensiones en el switch.
Subir Imágenes a una Base de Datos
Subir una imagen que será persistida en un campo en una base de datos requerirá de algunas vueltas mas en el procesamiento del archivo que es subido al servidor.
Para esto se ha creado una clase que actuara a manera de Data Access y contendrá las consultas que inserten la información en la tabla.
Para guardar la información se hará uso del siguiente método
public static void GuardarImagen(string nombrearchivo, int length, byte[] imagen)
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
{
conn.Open();
string query = @"INSERT INTO Imagenes (nombre, length, imagen)
VALUES (@name, @length, @imagen)";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@name", nombrearchivo);
cmd.Parameters.AddWithValue("@length", length);
SqlParameter imageParam = cmd.Parameters.Add("@imagen", System.Data.SqlDbType.Image);
imageParam.Value = imagen;
cmd.ExecuteNonQuery();
}
}
la pagina ~/GuardarImagen/SubirImagen.aspx contiene un control FileUpload que seleccionara el archivo a subir en el cliente y mediante un botón se realizara la siguiente acción:
protected void btnGuardar_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
using (BinaryReader reader = new BinaryReader(FileUpload1.PostedFile.InputStream))
{
byte[] image = reader.ReadBytes(FileUpload1.PostedFile.ContentLength);
ImagenesDAL.GuardarImagen(FileUpload1.FileName, FileUpload1.PostedFile.ContentLength , image);
}
CargarListadImagenes();
}
}
Como puede visualizarse en el código se toma la información proveniente del upload y se invoca al método creado en el paso previo para insertar el registro, a este método se le pasa la imagen como un array de byte.
Listar archivos desde la base de datos
Bien ahora toca el momento de mostrar la información que es subida en el paso previo, para ello nuevamente se dispone de un GridView que es cargada en el Page_Load
public partial class SubirImagen : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
CargarListadImagenes();
}
private void CargarListadImagenes()
{
GridView1.DataSource = ImagenesDAL.GetImagenList();
GridView1.DataBind();
}
}
Lo interesante de este código esta en la línea 13, en donde nuevamente se hace uso de de un método provisto por la clase de acceso a datos.
public static List<Imagenes> GetImagenList()
{
List<Imagenes> lista = new List<Imagenes>();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
{
conn.Open();
string query = @"SELECT Id, Nombre, Length
FROM Imagenes";
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
Imagenes img = new Imagenes(
Convert.ToInt32(reader["Id"]),
Convert.ToString(reader["nombre"]),
Convert.ToInt32(reader["length"]));
lista.Add(img);
}
}
return lista;
}
Este método tiene algo particular devuelve una lista de un tipo de nombre “Imagenes”, la cual resulta ser una clase custom creada para hacer mas simple trabajar con la información obtenida en la consulta.
public class Imagenes
{
public Imagenes(int id, string nombre, int length)
{
this.Id = id;
this.Nombre = nombre;
this.Length = length;
}
public int Id { get; set; }
public int Length { get; set; }
public string Nombre { get; set; }
public byte[] Imagen { get; set; }
}
El control de grilla hará uso de las propiedades de esta clase para mostrar la información
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4"
ForeColor="#333333" GridLines="None">
<RowStyle BackColor="#EFF3FB" />
<Columns>
<asp:BoundField DataField="Nombre" HeaderText="Nombre Archivo" />
<asp:BoundField DataField="Length" HeaderText="Tamaño" />
<asp:TemplateField HeaderText="Descargar">
<ItemTemplate>
<asp:HyperLink ID="descarga" runat="server" NavigateUrl='<%# Eval("Id", "~/GuardarImagen/Download.aspx?id={0}") %>'>
<img src="../imagenes/download.gif" alt="" width="30px" height="30px" style="border-width:0px;" />
</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<EditRowStyle BackColor="#2461BF" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
En esta oportunidad hay un detalle a remarcar en cuanto a la descarga, o acceso a la imagen desde el sitio, existen dos formas de tratar el archivo
- por medio de una pagina que reciba el id del registro que contiene la imagen
- por medio de un hadler de asp.net
Descargar la imagen por medio de una pagina
En los tag que forman el GridView seguro se ha observado que el link de descarga apunta a una url : ~/GuardarImagen/Download.aspx?id={0}
este es el documento que tomara el id informado por QueryString, realizaría la consulta tomando el archivo de imagen y lo pondrá en el objeto Response listo para enviarlo al cliente.
public partial class Download : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int id = Convert.ToInt32(Request.QueryString["id"]);
Imagenes imagen = ImagenesDAL.GetImagenById(id);
Response.Clear();
Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", imagen.Nombre));
switch (Path.GetExtension(imagen.Nombre).ToLower())
{
case ".jpg":
Response.ContentType = "image/jpg";
break;
case ".gif":
Response.ContentType = "image/gif";
break;
case ".png":
Response.ContentType = "image/png";
break;
}
Response.BinaryWrite(imagen.Imagen);
Response.End();
}
}
Es interesante remarcar que aquí se hace uso de otro método de la clase de acceso a datos, en este caso para tomar solo la información de una imagen.
public static Imagenes GetImagenById(int Id)
{
Imagenes img = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
{
conn.Open();
string query = @"SELECT Id, Nombre, Length, Imagen
FROM Imagenes
WHERE Id = @id";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@id", Id);
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
img = new Imagenes(
Convert.ToInt32(reader["Id"]),
Convert.ToString(reader["nombre"]),
Convert.ToInt32(reader["length"]));
img.Imagen = (byte[])reader["Imagen"];
}
}
return img;
}
Descargar la imagen por medio de un handler
Si bien el código interno que usara el handler será igual al de la pagina, este requieres algunos comentarios.
El primero es que a modo de ejemplo el uso de handler se desarrollo en la pagina ~/GuardarImagen/ListarImagenes.aspx
Esta pagina posee una grilla en donde una de las columnas hay un control de imagen:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
CellPadding="4" ForeColor="#333333" GridLines="None">
<RowStyle BackColor="#EFF3FB" />
<Columns>
<asp:BoundField HeaderText="Nombre" DataField="nombre" />
<asp:TemplateField HeaderText="Imagen">
<ItemTemplate>
<img alt="" src="<%# Eval("Id", "Image.ashx?id={0}") %>" width="200px" height="200px" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<EditRowStyle BackColor="#2461BF" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
la línea importante es
<
img
alt
=
""
src
=
"<%# Eval("
Id", "Image.ashx?id={0}") %>" width="200px" height="200px" />
en esta se hace uso de un nombre y extensión especifico, en realidad lo importante es la extensión, el nombre en este caso puede ser cualquier otro y seguiría funcionando.
La imagen en este caso esta utilizando un handler para cargar la imagen, el mismo esta en la clase: HttpImageHandler.cs
public class HttpImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
int id = Convert.ToInt32(context.Request.Params["id"]);
Imagenes imagen = ImagenesDAL.GetImagenById(id);
context.Response.Clear();
context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", imagen.Nombre));
switch (Path.GetExtension(imagen.Nombre).ToLower())
{
case ".jpg":
context.Response.ContentType = "image/jpg";
break;
case ".gif":
context.Response.ContentType = "image/gif";
break;
case ".png":
context.Response.ContentType = "image/png";
break;
}
context.Response.BinaryWrite(imagen.Imagen);
context.Response.End();
}
public bool IsReusable
{
get { return false; }
}
}
Lo interesando te este código es que al igual que la pagina de descarga, se tiene acceso al objeto Reponse, solo que en esta oportunidad es a través de HttpContext.
Además debe tenerse en cuenta para que el handler funcione es necesario agregar una línea en el archivo web.config
en este debería agregarse:
<add verb="*" path="*.ashx" type="GuardarImagenBaseDatos.GuardarImagen.HttpImageHandler"/>
El atributo type debe incluir todo el namespace, mas el nombre de la clase, debe tenerse en cuenta que esto puede variar en cada desarrollo dependiendo de donde se ubique la clase del handler.
Aclaración
Para que el ejemplo funcione es necesario contar con Sql Server Express instalado en la PC donde se ejecute el código, y el servicio debe estar iniciado