Introducción
Este articulo podría considerarse la continuación de:
[ASP.NET] - Guardar Imagen base de datos
básicamente se extenderá el articulo anterior aportando los temas:
- subir un archivo a una carpeta dentro del sitio
- validación de las extensiones que puede subirse
- listar los archivos de la carpeta
- distintas formas de representar el link de descarga
- link directo al archivo
- link a una pagina que administrara la descarga
- borrado del archivo de la carpeta del sitio
- subir el archivos a una base de datos
- listar los registro de la tabla
- distintas formas de implementar la descarga
- usando un handler
- usando una página que administre la descarga
- eliminar registro de la base de datos, por ende el archivo
Subir un archivo a una carpeta del sitio
Para esta primera sección se trabajara con el contenido desarrollado en la carpeta “GuardarEnCarpeta” del proyecto que se puede descargar en el articulo, la carpeta contiene la página “ListarArchivos.aspx”, cuya funcionalidad será la de subir (FileUpload) y además listas los archivos (GridView), de una carpeta determinada dentro de la estructura del sitio web.
<form id="form1" runat="server"> <div> <asp:FileUpload ID="FileUpload1" runat="server" /> <br /> <br /> <asp:Button ID="btnSubirArchivo" runat="server" Text="Subir Archivo" onclick="btnSubirArchivo_Click" /> <br /> <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" ErrorMessage="Solo pdf, doc o xls son permitidos." ValidationExpression="^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))+(.pdf|.doc|xls)$" ControlToValidate="FileUpload1"> </asp:RegularExpressionValidator> <br /> <br /> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" onrowdeleting="GridView1_RowDeleting" DataKeyNames="Name"> <RowStyle BackColor="#EFF3FB" /> <Columns> <asp:TemplateField HeaderText="Eliminar" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:ImageButton ID="imgEliminar" runat="server" CommandName="Delete" ImageUrl="~/imagenes/delete.png" Width="24px" Height="24px"/> </ItemTemplate> </asp:TemplateField> <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" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:HyperLink ID="descarga" runat="server" NavigateUrl='<%# Eval("Name", "~/GuardarEnCarpeta/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> </div> </form>
Es importante resaltar algunos puntos de este código.
Validación en las extensión que pueden ser subidas al sitio
Se hace uso del control RegularExpressionValidator, para proporcionar una validación del lado del cliente, que impida subir archivos en una extensión invalida, o no deseada.
<asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" ErrorMessage="Solo pdf, doc o xls son permitidos." ValidationExpression="^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))+(.pdf|.doc|xls)$" ControlToValidate="FileUpload1"> </asp:RegularExpressionValidator>
Dos formas diferentes de exponer el link de descarga
El gridview que lista los archivos de la carpeta expone dos link de descarga del archivo listado, uno de ellos al presionar el nombre:
<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>
Prestar atención a la definición del NavidateUrl, en este la url especifica concretamente al archivo, por medio del path relativo usando el ~/, esto se puede lograr porque al archivo esta físicamente alojado en una carpeta en al estructura del sitio publicado.
La otra alternativa se presenta al presionar sobre la imagen de descarga:
<asp:TemplateField HeaderText="Descargar" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:HyperLink ID="descarga" runat="server" NavigateUrl='<%# Eval("Name", "~/GuardarEnCarpeta/Download.aspx?filename={0}") %>'> <img src="../imagenes/download.gif" alt="" width="30px" height="30px" style="border-width:0px;" /> </asp:HyperLink> </ItemTemplate> </asp:TemplateField>
En este otro caso se define una pagina web “Download.aspx” con un valor que pasara por querystring, esta pagina será la responsable de tomar el archivo desde su lugar físico y lo enviara en el Response.
Es importante en este punto analizar que acciones toma esta pagina.
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)); Response.ContentType = "application/octet-stream"; Response.WriteFile(Server.MapPath(Path.Combine("~/files", filename))); Response.End(); } }
El código que implementa es muy simple, solo toma el archivo que recibe por la url, define información para la respuesta y escribe en el Response el archivo para ser enviado.
Un punto no menor es la definición del ContentType del tipo “application/octet-stream”, este forzara la descarga mostrando el cuadro de download al usuario para que seleccione donde quiere descargarlo. Si en lugar de usar este se hubiera definido el tipo concreto para carda archivo, al realizarse la descarga se mostraría embebido el documento en el browser, por supuesto esto sucederá si el usuario posee un editor para este documento.
Eliminación del archivo subido el sitio
La eliminación del archivo esta implementada en los botones de gridview, pero para esto funcione se definen varios puntos, el primero tiene que ver con la columna en el grid
<asp:TemplateField HeaderText="Eliminar" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:ImageButton ID="imgEliminar" runat="server" CommandName="Delete" ImageUrl="~/imagenes/delete.png" Width="24px" Height="24px"/> </ItemTemplate> </asp:TemplateField>
la cual es un ImagenButton con el CommandName = “Delete”, que lleve el nombre “Delete” no es arbitrario, sino que con este nombre lanzara el evento RowDeleting del gridview, es por eso que se asocia el evento onrowdeleting="GridView1_RowDeleting" además de las propiedad DataKeyNames="Name", para poder tomar el nombre del archivo que se quiere eliminar.
Si ahora se analiza el evento:
protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e) { //se obtiene el nombre de campo definido en el DataKeyNames del gridview string fileName = Convert.ToString(GridView1.DataKeys[e.RowIndex].Value); //se define el path fisico del archivo string fullPath = Path.Combine(Server.MapPath("~/files"), fileName); File.Delete(fullPath); Response.Redirect("ListarArchivos.aspx"); }
en este se hace uso del DataKeys, para obtener el nombre del archivo, esto se logra gracias al DataKeyNames definido con el campo del nombre del archivo, ahora solo queda obtener la ruta física y proceder a eliminar.
Subir un archivo a la base de datos
En esta otra sección cambiaremos de carpeta del trabajo dentro del sitio, ahora sera “GuardarEnDb”, aquí también se cuenta con un GridView para listar los archivos registrados en la db, y el FileUpload para subirlos al sitio.
<form id="form1" runat="server"> <div> <asp:FileUpload ID="FileUpload1" runat="server" /> <br /> <br /> <asp:Button ID="btnGuardar" runat="server" Text="Guardar Archivo" OnClick="btnGuardar_Click" /> <br /> <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" ErrorMessage="Solo pdf, doc o xls son permitidos." ValidationExpression="^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))+(.pdf|.doc|xls)$" ControlToValidate="FileUpload1"> </asp:RegularExpressionValidator> <br /> <br /> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" DataKeyNames="Id" onrowdeleting="GridView1_RowDeleting"> <RowStyle BackColor="#EFF3FB" /> <Columns> <asp:TemplateField HeaderText="Eliminar" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:ImageButton ID="imgEliminar" runat="server" CommandName="Delete" ImageUrl="~/imagenes/delete.png" Width="24px" Height="24px"/> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Nombre Archivo"> <ItemTemplate> <asp:HyperLink ID="nombre" runat="server" NavigateUrl='<%# Eval("Id", "~/DescargaArchivo.ashx?id={0}") %>' Text='<%# Eval("Nombre") %>'> </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("Id", "~/GuardarEnDb/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> </div> </form>
A simple vista pareciera idéntico al ejemplo tratado en el punto 2 del articulo, pero hay detalles que deben analizarse, porque hay unas cuantas diferencias.
Dos formas diferentes de exponer el link de descarga
Aquí también nos encontramos con distintas formas de descarga, pero en este caso al tratarse de una base de datos ya no se puede acceder directo al archivo por su url, será necesario contar con intermediarios que obtengan el archivo desde la db y lo envíen como respuesta.
Una de esta formas se define por medio de un handler, en donde la columna en el grid poseerá una url al mismo.
<asp:TemplateField HeaderText="Nombre Archivo"> <ItemTemplate> <asp:HyperLink ID="nombre" runat="server" NavigateUrl='<%# Eval("Id", "~/DescargaArchivo.ashx?id={0}") %>' Text='<%# Eval("Nombre") %>'> </asp:HyperLink> </ItemTemplate> </asp:TemplateField>
Nótese que lo importante aquí no es el nombre que se use para referirse al handler, ya que este puede ser cualquiera, lo importante es la extensión usada (en este caso ashx), ya que la misma será definida en el web.config, en la línea:
<httpHandlers> . . <add verb="*" path="*.ashx" type="GuardarArchivoBaseDatos.GuardarArchivo.HttpImageHandler"/> </httpHandlers>
La implementación del handler se encuentra en la clase HttpImageHandler.cs, dentro del código del mismo se simula el comportamiento que tendría la definición del link si se lo define directo al archivo, es por eso que se define un ContentType concreto para cada extensión especifica de los archivos soportados.
public class HttpImageHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { int id = Convert.ToInt32(context.Request.Params["id"]); Archivo archivo = ArchivosDAL.GetById(id); context.Response.Clear(); context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", archivo.Nombre)); switch (Path.GetExtension(archivo.Nombre).ToLower()) { case ".pdf": context.Response.ContentType = "application/pdf"; break; case ".doc": context.Response.ContentType = "application/msword"; break; case ".xls": context.Response.ContentType = "application/vnd.ms-excel"; break; } context.Response.BinaryWrite(archivo.ContenidoArchivo); context.Response.End(); } public bool IsReusable { get { return false; } } }
Nota: podrías haberse definido directamente el “application/octet-stream” en el hadler, pero se realizo de esta forma para mostrar distintas variantes que pueden aplicarse.
Es en la otra alternativa se define la página “Download.aspx”, pero esta tiene algunas diferencias con respeto a su semejante cuando se trabaja con archivos.
<asp:TemplateField HeaderText="Descargar"> <ItemTemplate> <asp:HyperLink ID="descarga" runat="server" NavigateUrl='<%# Eval("Id", "~/GuardarEnDb/Download.aspx?id={0}") %>'> <img src="../imagenes/download.gif" alt="" width="30px" height="30px" style="border-width:0px;" /> </asp:HyperLink> </ItemTemplate> </asp:TemplateField>
Si bien pareciera engañar la igualdad con el ejemplo del punto 2.2, este tiene sus diferencias, una muy clara es el uso del campo “Id” como parámetro de la url del link, al usar una base de datos estamos trabajando con registro y los mismo se identifican por su clave.
public partial class Download : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { int id = Convert.ToInt32(Request.QueryString["id"]); Archivo archivo = ArchivosDAL.GetById(id); Response.Clear(); Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", archivo.Nombre)); Response.ContentType = "application/octet-stream"; Response.BinaryWrite(archivo.ContenidoArchivo); Response.End(); } }
Se toma el id enviado en el querystring, se recupera la información del archivo por medio de una consulta a la DB y luego de definir el ContentType, se envía el array de byte por el Response, nótese que aquí no se escribe un archivo porque no se tiene uno, sino que se opera directamente con los byte.
Eliminación del archivo subido el sitio
Las diferencia en este punto con lo visto en el 2.3, es que aquí se debe hacer uso del id de la entidad que se quiere eliminar, y no el nombre del archivo, es por eso que se define el DataKeyNames, con el “Id”, y luego en el código:
protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e) { //se obtiene el nombre de campo definido en el DataKeyNames del gridview int id = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value); ArchivosDAL.DeleteById(id); CargarListadImagenes(); }
se toma este para ejecuta la consulta de DELETE del registro en la db
Código de ejemplo
El desarrollo del ejemplo fue realizado con Visual Studio 2008, y la db empleada es Sql Server 2008 Express, el mdf de la misma se encuentra en la carpeta App_Data, y la cadena de conexión se define en el Web.config
[C#] |
[VB.NET] |