Devolver un archivo para Ver/Descargar en ASP.NET MVC

Resuelto Nick Albrecht asked hace 13 años • 9 respuestas

Tengo un problema al enviar archivos almacenados en una base de datos al usuario en ASP.NET MVC. Lo que quiero es una vista que enumere dos enlaces, uno para ver el archivo y permitir que el tipo MIME enviado al navegador determine cómo debe manejarse, y el otro para forzar una descarga.

Si elijo ver un archivo llamado SomeRandomFile.baky el navegador no tiene un programa asociado para abrir archivos de este tipo, entonces no tengo ningún problema con que adopte de forma predeterminada el comportamiento de descarga. Sin embargo, si elijo ver un archivo llamado SomeRandomFile.pdfo SomeRandomFile.jpgquiero que el archivo simplemente se abra. Pero también quiero mantener un enlace de descarga a un lado para poder forzar un mensaje de descarga independientemente del tipo de archivo. ¿Esto tiene sentido?

Lo he intentado FileStreamResulty funciona para la mayoría de los archivos, su constructor no acepta un nombre de archivo de forma predeterminada, por lo que a los archivos desconocidos se les asigna un nombre de archivo según la URL (que no sabe la extensión que se debe dar según el tipo de contenido). Si fuerzo el nombre del archivo especificándolo, pierdo la capacidad del navegador de abrir el archivo directamente y aparece un mensaje de descarga. ¿Alguien más ha encontrado esto?

Estos son los ejemplos de lo que he probado hasta ahora.

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

¿Alguna sugerencia?


ACTUALIZACIÓN: Esta pregunta parece tocar la fibra sensible de mucha gente, así que pensé en publicar una actualización. La advertencia en la respuesta aceptada a continuación que Oskar agregó con respecto a los caracteres internacionales es completamente válida, y la he presionado varias veces debido al uso de la ContentDispositionclase. Desde entonces actualicé mi implementación para solucionar este problema. Si bien el código a continuación proviene de mi encarnación más reciente de este problema en una aplicación ASP.NET Core (Full Framework), también debería funcionar con cambios mínimos en una aplicación MVC anterior, ya que estoy usando la System.Net.Http.Headers.ContentDispositionHeaderValueclase.

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}
Nick Albrecht avatar Apr 29 '11 07:04 Nick Albrecht
Aceptado
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

NOTA: Este código de ejemplo anterior no tiene en cuenta correctamente los caracteres internacionales en el nombre del archivo. Consulte RFC6266 para conocer la estandarización relevante. Creo que las versiones recientes del File()método de ASP.Net MVC y la ContentDispositionHeaderValueclase tienen en cuenta esto adecuadamente. - Óscar 25-02-2016

Darin Dimitrov avatar Apr 29 '2011 09:04 Darin Dimitrov

Tuve problemas con la respuesta aceptada debido a que no había ningún tipo de sugerencia en la variable "documento": var document = ...así que publico lo que funcionó para mí como alternativa en caso de que alguien más tenga problemas.

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}
ooXei1sh avatar Jun 17 '2015 13:06 ooXei1sh

Para ver el archivo (txt por ejemplo):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

Para descargar el archivo (txt por ejemplo):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

nota: para descargar el archivo debemos pasar el argumento fileDownloadName

 avatar May 04 '2016 16:05

Creo que esta respuesta es más clara (basada en https://stackoverflow.com/a/3007668/550975 )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }
Serj Sagan avatar Nov 07 '2013 19:11 Serj Sagan