
Lo prometido es deuda (lean los comentarios de
este otro post), así que vamos con el desarrollo del post.
En esta ocasión vamos a realizar el proceso de:
1.- Guardar, en una carpeta del servidor, archivos, los cuales deben guardarse con otros nombres (nombres autogenerados, por ejemplo: 3F2504E0-4F89-11D3-9A0C-0305E82C3301).
2.- Guardar en la base de datos, el nombre original del archivo y el nuevo nombre.
3.- Recuperar los nombres y rutas de los archivos subidos al servidor y listarlos en un gridview.
4.- Basado en los puntos 2 y 3, permitir descargar los archivos con su original nombre.
Para completar este ciclo voy a crear en la base de datos una tabla llamada document_attachment y los respectivos procedimientos almacenados para la gestión de archivos.
Entonces, este es el script T-SQL que uso para el caso ( una tabla y 3 procedimientos almacenados):
CREATE TABLE [dbo].[document_attachment](
[attachmentid] [int] IDENTITY(1,1) NOT NULL,
[filename] [varchar](250),
[fileguid] [varchar](50),
CONSTRAINT [pk_document_attachment] PRIMARY KEY CLUSTERED
([attachmentid] ASC)
)
GO
--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_GetAll]
AS
SELECT
[attachmentid],
[filename],
[fileguid]
FROM [dbo].[document_attachment]
GO
--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_Insert]
@attachmentid
int output,
@filename
varchar(250),
@fileguid
varchar(50)
AS
INSERT INTO[dbo].[document_attachment](
[filename],
[fileguid]
)
VALUES(
@filename,
@fileguid
)
SET @attachmentid =
SCOPE_IDENTITY()
GO
--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_GetByGuid]
@guid
varchar(50)
AS
SELECT
[filename],
[fileguid]
FROM [dbo].[document_attachment]
WHERE [fileguid] = @guid
--------------------------------------------------
Antes de pasar al código ASP.NET, les recomiendo que guarden la cadena de conexión a la base de datos en el web config (para el ejemplo está sin encriptar, …pero uds deben encriptarlo)
<connectionStrings>
<add name="DB" connectionString="server=.\SQLEXPRESS;database=my_database;uid=sa;pwd=xxxxxxxxxxxxxxx" />
</connectionStrings>
Otra cosa, deben crear una carpeta llamada Uploads (en la raiz de su aplicación), que es donde se guardarán los archivos que se suben… a esta carpeta hay que darle permisos de lectura/escritura al usuario SERVICIO DE RED.
Ahora sí, necesitamos dos páginas aspx:
1.-
Uploader.aspx, que es la página que permite subir los archivos al servidor e, inmediatamente después actualiza el contenido del gridview con los archivos subidos.
2.-
Downloader.aspx, que es la página intermedia para descargar archivos… gracias a esta página podemos evitar que el usuario sepa desde qué carpeta se descargan los archivos y además en el código de esta página pueden hacer las verificaciones que quieran antes de permitirle al usuario descargar el archivo. Tú me preguntarás: puedo usar un archivo ashx (que es más ligero) en lugar de una pagina aspx para descargar? la respuesta es sí.
Este es el código completo:
Uploader.aspx
<!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>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div style="width: 350px; background-color: #f2ceea; padding: 10px; margin: 20px">
Seleccione archivo:<asp:FileUpload ID="FileUpload1" runat="server" />
<hr />
<asp:Button ID="btnUpload" runat="server" Text="Guardar archivo" />
</div>
<asp:GridView ID="griddocument_attachment" BackColor="lightgreen" Width="400px" runat="server"
AutoGenerateColumns="False" DataKeyNames="fileguid" HeaderStyle-HorizontalAlign="Left"
HeaderStyle-VerticalAlign="Top" RowStyle-VerticalAlign="Top">
<Columns>
<asp:TemplateField HeaderStyle-Width="3%">
<ItemTemplate>
<asp:Image ID="Image1" ImageUrl="~/images/file.png" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Archivo">
<ItemTemplate>
<%# Eval("filename")%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Descargar" HeaderStyle-Width="5%">
<ItemTemplate>
<asp:HyperLink ID="hplAttachment" Text='<%# Eval("filename")%>' NavigateUrl='<%# String.Format("~/Downloader.aspx?guid={0}", Eval("fileguid")) %>'
ImageUrl="~/images/download.png" runat="server"></asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</form>
</body>
</html>
En el gridview, el hyperlink con la imagen download.png es quien tiene el enlace de descarga, parecido a esto:
http://localhost/Test_UI/Downloader.aspx?guid=ee4a00fa-cfa9-490e-a65c-12d652fd2c00
código VB.NET:
Imports System.Data
Imports System.Data.SqlClient
Partial Public Class Uploader
Inherits System.Web.UI.Page
Private GlobalDBConnection As String = ConfigurationManager.ConnectionStrings("DB").ConnectionString
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack Then Return
document_attachment_GetAll()
End Sub
Protected Sub btnUpload_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnUpload.Click
If Not FileUpload1.HasFile Then Return
Dim fileguid As String = Guid.NewGuid.ToString
Dim FileName As String = FileUpload1.FileName
Try
FileUpload1.SaveAs(Server.MapPath("./Uploads/") & fileguid)
Catch
'error....
Return
End Try
Dim i As Integer = document_attachment_Insert(FileName, fileguid)
If i = 0 Then
'error....
Else
document_attachment_GetAll()
End If
End Sub
Sub document_attachment_GetAll()
Dim connection As New SqlConnection(GlobalDBConnection)
Dim command As New SqlDataAdapter("document_attachment_GetAll", connection)
command.SelectCommand.CommandType = CommandType.StoredProcedure
Dim dt As New DataTable()
command.Fill(dt)
griddocument_attachment.DataSource = dt
griddocument_attachment.DataBind()
End Sub
Function document_attachment_Insert(ByVal filename As String, ByVal fileguid As String) As Integer
Dim connection As New SqlConnection(GlobalDBConnection)
Dim command As New SqlCommand("document_attachment_Insert", connection)
command.CommandType = CommandType.StoredProcedure
Dim p_attachmentid As New SqlParameter("@attachmentid", SqlDbType.Int)
p_attachmentid.Direction = ParameterDirection.Output
command.Parameters.Add(p_attachmentid)
command.Parameters.AddWithValue("@filename", filename)
command.Parameters.AddWithValue("@fileguid", fileguid)
command.Connection.Open()
Try
command.ExecuteNonQuery().ToString()
Catch
Return 0
Finally
command.Connection.Close()
End Try
If Not IsDBNull(p_attachmentid.Value) Then
Return CInt(p_attachmentid.Value)
Else
Return 0
End If
End Function
End Class
Downloader.aspx
en el lado aspx tiene absolutamente nada, excepto el tag Page:
<%@ Page Language="vb" …………………………………………………… %>
código VB.NET:
Imports System.Data
ImportsSystem.Data.SqlClient
Partial Public Class Downloader
Inherits System.Web.UI.Page
Private GlobalDBConnection
As String= ConfigurationManager.ConnectionStrings(
"DB").ConnectionString
Protected SubPage_Load(
ByVal sender
As Object,
ByVal e
As System.EventArgs)
Handles Me.Load
Dim guid
As String= Request.QueryString(
"guid")
If String.IsNullOrEmpty(guid)
Then Return
' aquí puedes poner las validaciones que quieras
'............ ....................
Dim dt
As DataTable = document_attachment_GetByGuid(guid)
If dt.Rows.Count = 0
Then Return
Dim OriginalFileName
As String= dt.Rows(0)(
"filename")
DownloadFile(Server.MapPath(
"./Uploads/") & guid, OriginalFileName)
End Sub
Public Sub DownloadFile(
ByVal FilePath
As String,
ByVal OriginalFileName
As String)
Dim fs
As IO.FileStream =
Nothing
'obtenemos el archivo del servidor
fs = IO.File.Open(FilePath, IO.FileMode.Open, IO.FileAccess.Read)
Dim byteBuffer(
CInt(fs.Length - 1))
As Byte
fs.Read(byteBuffer, 0,
CInt(fs.Length))
fs.Close()
Using ms
As New IO.MemoryStream(byteBuffer)
'descargar con su nombre original
Response.AddHeader(
"Content-Disposition",
"attachment; filename=" & OriginalFileName)
ms.WriteTo(Response.OutputStream)
End Using
End Sub
Function document_attachment_GetByGuid(
ByVal guid
As String)
As DataTable
Dim connection
As New SqlConnection(GlobalDBConnection)
Dim command
As New SqlDataAdapter(
"document_attachment_GetByGuid", connection)
command.SelectCommand.CommandType = CommandType.StoredProcedure
command.SelectCommand.Parameters.AddWithValue(
"@guid", guid)
Dim dt
As New DataTable()
command.Fill(dt)
Return dt
End Function
End Class
ACTUALIZACIÓN:
Debido a las preguntas que constantemente estoy recibiendo acerca de este post, veo que no ha quedado muy claro... entonces, estoy añadiendo el proyecto completo para que lo descarguen y lo analicen con más calma.
Debido a que estoy incluyendo la base de datos en la carpeta app_data, lo único que tendrían que modificar en el we.config antes de correr el proyecto es el nombre del servidor de su bd... osea reemplacen .\SS2008 por el nombre del servidor que Uds le hayan puesto.
Es todo, espero que les sea de utilidad.
;)