ASP.NET MVC subiendo y descargando Archivos
Publicado por: Eder Oscar Lázaro Rodriguez (edoswit)En Web Forms, es tan facil como cuando se arrastra un control FileUpload en el diseñador. El formulario HTML resultante que envuelve toda la página está decorado con un atributo adicional: enctype = "multipart / form-data". Este el FileUpload sí se representa en html es como un input con type=file. Dentro de una vista en MVC, hay un buen número de maneras de configurar esta opción. La primera es con HTML:
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="FileUpload1" /><br />
<input type="submit" name="Submit" id="Submit" value="Upload" />
</form>
Observe que la etiqueta <form> incluye el atributo enctype, y el atributo method en post. Esto es necesario porque la forma por defecto será presentado a través del método HTTP GET. En el enfoque siguiente, utilizando el Html.BeginForm () hace que el método de extensión html es exactamente el mismo cuando se solicita la página:
<% using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"}))
{%>
<input type="file" name="FileUpload1" /><br />
<input type="submit" name="Submit" id="Submit" value="Upload" />
<% }%>
Observe el atributo de nombre del elemento <input type="file">, la página resultante debería ser como este:
Así que ahora podemos navegar en un archivo local y clic en el botón de enviar para subirlo al servidor web. Lo que se necesita luego es la manera de administrar el archivo en el servidor. Cuando se usa un control FileUpload, por lo general, ver el código que comprueba si un archivo ha sido cargado en realidad, utilizando el metodo FileUpload.HasFile(). Sin embargo, un método de extensión rápida puede hacerse cargo de:
public static bool HasFile(this HttpPostedFileBase file)
{
return (file != null && file.ContentLength > 0) ? true : false;
}
En la clase del controlador, se ve que tiene una solicitud de objeto como una propiedad, que es de tipo HttpRequestBase. Este es un contenedor para una petición HTTP, y expone las propiedades, incluyendo una colección de archivos (en realidad una colección de tipo HttpFileCollectionBase). El método de extensión de los controles es para asegurarse de que hay algo allí, y que tiene algún contenido. Poner esto en uso dentro de la acción del controlador es muy sencillo:
public class HomeController : Controller
{
public ActionResult Index() { foreach (string upload in Request.Files)
{
if (!Request.Files[upload].HasFile()) continue;
string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string filename = Path.GetFileName(Request.Files[upload].FileName);
Request.Files[upload].SaveAs(Path.Combine(path, filename));
}
return View();
}
}
Subiendo Multiples Archivos:
Al saber del hecho de que Request.Files es una colección, nos sugiere que puede acomodar más de un archivo.. Si cambia la presentacion de la pagina original a esta:
<% using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"}))
{%>
<input type="file" name="FileUpload1" /><br />
<input type="file" name="FileUpload2" /><br />
<input type="file" name="FileUpload3" /><br />
<input type="file" name="FileUpload4" /><br />
<input type="file" name="FileUpload5" /><br />
<input type="submit" name="Submit" id="Submit" value="Upload" />
<% }%>
El código en el controlador de la acción ya esta para todos los controles de envío de archivos, de modo que no se necesitan cambios para que funcione con la carga de archivos múltiples. Observe que cada entrada tiene un atributo de nombre diferente. Si usted necesita hacer referencia a ellos individualmente, que es lo que usa. Por ejemplo, para hacer referencia a la tercera, debería llegar hasta él utilizando Request.Files["FileUpload3"].
Guardar en una base de datos
La siguiente porción de código es realmente indicativo. Cuenta con ADO.NET en una acción del controlador. Como todos sabemos, esto no es realmente lo que se debe hacer. El código de acceso a la base de datos pertenece a la capa de datos de acceso en algún lugar dentro del modelo. Sin embargo, el código debería dar a la gente un punto de partida si desea guardar los archivos subidos a una base de datos. En primer lugar, he creado una base de datos (FileTest) y añadió una tabla: FileStore:
CREATE TABLE [dbo].[FileStore](
[ID] [int] IDENTITY(1,1) NOT NULL,
[FileContent] [image] NOT NULL,
[MimeType] [nvarchar](50) NOT NULL,
[FileName] [nvarchar](50) NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
El campo FileContent es un tipo de datos de imagen, y es donde los datos binarios que forma el archivo se almacena. La Accion del Index se cambia a lo siguiente:
public ActionResult Index()
{
foreach (string upload in Request.Files)
{
if (!Request.Files[upload].HasFile()) continue;
string mimeType = Request.Files[upload].ContentType;
Stream fileStream = Request.Files[upload].InputStream;
string fileName = Path.GetFileName(Request.Files[upload].FileName);
int fileLength = Request.Files[upload].ContentLength;
byte[] fileData = new byte[fileLength];
fileStream.Read(fileData, 0, fileLength);
const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
using (var conn = new SqlConnection(connect))
{
var qry = "INSERT INTO FileStore (FileContent, MimeType, FileName) VALUES (@FileContent, @MimeType, @FileName)";
var cmd = new SqlCommand(qry, conn);
cmd.Parameters.AddWithValue("@FileContent", fileData);
cmd.Parameters.AddWithValue("@MimeType", mimeType);
cmd.Parameters.AddWithValue("@FileName", fileName);
conn.Open();
cmd.ExecuteNonQuery();
}
}
return View();
}
El código comprueba cada uno de los controles para ver si tiene archivo. Desde allí, los extractos de 3 cosas de información: el nombre del archivo, el tipo de MIME (qué tipo de archivo es) y los datos binarios que se fluye como parte de la solicitud HTTP. Los datos binarios se transfiere a una matriz de bytes, que es lo que se almacena en el campo de tipo de datos de imagen en la base de datos. El tipo MIME y el nombre son importantes para cuando el archivo se devuelve a un usuario. Vamos a ver en la parte siguiente.
Descargando los archivos por el usuario
¿Cómo volver a entregar los archivos a los usuarios? esto dependerá de cómo los tengas almacenadas en primer lugar. Si los tiene almacenados en una base de datos, lo normal es leer el archivo y devolverlo al usuario. Si se almacenan en un disco, usted puede simplemente ofrecer un enlace a ellos. Siempre que necesita transmitir un archivo en el navegador, se utilizará una de las sobrecargas del método File(). Hay 3 tipos de retorno diferente del método File(): FilePathResult, FileContentResult y FileStreamResult. El primero transmiten un archivo directamente desde el disco, el segundo envía una matriz de bytes al cliente, mientras que el tercero envía el contenido de un objeto Stream que se ha generado y abierto.
public FileContentResult GetFile(int id)
{
SqlDataReader rdr; byte[] fileContent = null;
string mimeType = "";string fileName = "";
const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
using (var conn = new SqlConnection(connect))
{
var qry = "SELECT FileContent, MimeType, FileName FROM FileStore WHERE ID = @ID";
var cmd = new SqlCommand(qry, conn);
cmd.Parameters.AddWithValue("@ID", id);
conn.Open();
rdr = cmd.ExecuteReader();
if (rdr.HasRows)
{
rdr.Read();
fileContent = (byte[])rdr["FileContent"];
mimeType = rdr["MimeType"].ToString();
fileName = rdr["FileName"].ToString();
}
}
return File(fileContent, mimeType, fileName);
}
La forma más fácil de invocar este método es proporcionar un enlace:
<a href="/GetFile/1">Click to get file</a>
Si los archivos de la base de datos son las imágenes, en lugar de un hipervínculo, sólo apuntan a la acción de un controlador en el atributo src de un elemento <img>:
<img src="/GetFile/1" alt="My Image" />
Vamos a echar un vistazo a la forma de utilizar simplemente el FilePathResult ahora. Esto se utiliza para transmitir archivos directamente desde el disco:
public FilePathResult GetFileFromDisk()
{
string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string fileName = "test.txt";
return File(path + fileName, "text/plain", "test.txt");
}
Y esto también es invocado a través de un hipervínculo simple:
<a href="/GetFileFromDisk">Click to get file</a>
La opción final - FileStreamResult puede ser usado para transmitir los archivos desde el disco también:
public FileStreamResult StreamFileFromDisk()
{
string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string fileName = "test.txt";
return File(new FileStream(path + fileName, FileMode.Open), "text/plain", fileName);
}
Entonces, ¿cuál es la diferencia entre FilePathResult y FileStreamResult y que se debe usar? La principal diferencia es que FilePathResult utiliza HttpResponse.TransmitFile para escribir el archivo a la salida HTTP. Este método no tiene el archivo en el buffer de memoria del servidor, por lo que debería ser una mejor opción para enviar archivos más grandes. Es muy parecido a la diferencia entre usar un DataReader o un DataSet. Por otro lado, puede que tenga que comprobar en el servidor que aloja su sitio web, como un error en TransmitFile que puede conducir a la entrega parcial de los archivos, o incluso el fracaso completo. FileStreamResult es un gran manera para transferir, por ejemplo, devolviendo imágenes en la memoria de gráfico generado por los controles de ASP.NET gráfico sin tener que guardarlos en el disco.








Que tal Eder, me parece muy interesante el post. Te comento que me encuentro desarrollando una aplicacion con asp.net y sql server y justamente la descarga de imagenes por parte del usuario me esta dando dolor de cabeza... de casualidad tendras un ejemplo del codigo que publicas?
Gracias
Saludos!
publicado el 05 de April del 2010, a las 4:30 PM