¿Cómo puedo presentar un archivo para descargar desde un controlador MVC?

109

En WebForms, normalmente tendría un código como este para permitir que el navegador presente una ventana emergente "Descargar archivo" con un tipo de archivo arbitrario, como un PDF, y un nombre de archivo:

Response.Clear()
Response.ClearHeaders()
''# Send the file to the output stream
Response.Buffer = True

Response.AddHeader("Content-Length", pdfData.Length.ToString())
Response.AddHeader("Content-Disposition", "attachment; filename= " & Server.HtmlEncode(filename))

''# Set the output stream to the correct content type (PDF).
Response.ContentType = "application/pdf"

''# Output the file
Response.BinaryWrite(pdfData)

''# Flushing the Response to display the serialized data
''# to the client browser.
Response.Flush()
Response.End()

¿Cómo realizo la misma tarea en ASP.NET MVC?

mgnoonan
fuente

Respuestas:

181

Devuelve un FileResulto FileStreamResultde tu acción, dependiendo de si el archivo existe o si lo creas sobre la marcha.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
tvanfosson
fuente
14
Este es un gran ejemplo de por qué ASP.NET MVC es increíble. Lo que antes tenía que hacer en 9 líneas de código de aspecto confuso se puede hacer en una línea. ¡Mucho más fácil!
Jon Kruger
Gracias tvanfosson, busqué la mejor solución para hacer esto, y esta es genial.
Mark Kadlec
1
Esto requiere una extensión de archivo en el nombre del archivo o, de lo contrario, ignorará por completo el nombre del archivo y el tipo de contenido y solo intentará transmitir el archivo al navegador. También utilizará el nombre de la página web si el navegador no reconoce el tipo de contenido (es decir, octet-stream) cuando fuerza la descarga y no tendrá ninguna extensión.
RichC
62

Para forzar la descarga de un archivo PDF, en lugar de ser manejado por el complemento PDF del navegador:

public ActionResult DownloadPDF()
{
    return File("~/Content/MyFile.pdf", "application/pdf", "MyRenamedFile.pdf");
}

Si desea permitir que el navegador maneje por su comportamiento predeterminado (complemento o descarga), simplemente envíe dos parámetros.

public ActionResult DownloadPDF()
{
    return File("~/Content/MyFile.pdf", "application/pdf");
}

Deberá utilizar el tercer parámetro para especificar un nombre para el archivo en el cuadro de diálogo del navegador.

ACTUALIZACIÓN: Charlino tiene razón, al pasar el tercer parámetro (nombre de archivo de descarga) Content-Disposition: attachment;se agrega al encabezado de respuesta Http. Mi solución fue enviar application\force-downloadcomo tipo mime, pero esto genera un problema con el nombre de archivo de la descarga por lo que se requiere el tercer parámetro para enviar un buen nombre de archivo, eliminando así la necesidad de forzar una descarga .

Guzart
fuente
6
Técnicamente, eso no es lo que está sucediendo. Técnicamente, cuando agrega el tercer parámetro, el marco MVC agrega el encabezado content-disposition: attachment; filename=MyRenamedFile.pdf; esto es lo que fuerza la descarga. Le sugiero que vuelva a poner el tipo MIME en application/pdf.
Charlino
2
Gracias Charlino, no me di cuenta de que el tercer parámetro estaba haciendo eso, pensé que era solo para cambiar el nombre del archivo.
guzart
2
+1 para actualizar su respuesta y explicar el tercer parámetro + Content-Disposition: attachment;relación.
Charlino
7

Puedes hacer lo mismo en Razor o en el controlador, así ...

@{
    //do this on the top most of your View, immediately after `using` statement
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", "attachment; filename=receipt.pdf");
}

O en el controlador ...

public ActionResult Receipt() {
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", "attachment; filename=receipt.pdf");

    return View();
}

Probé esto en Chrome e IE9, ambos están descargando el archivo pdf.

Probablemente debería agregar que estoy usando RazorPDF para generar mis PDF. Aquí hay un blog al respecto: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
fuente
4

Debería mirar el método File del Controller. Para eso es exactamente. Devuelve FilePathResult en lugar de ActionResult.

Martín Peck
fuente
3

mgnoonan,

Puede hacer esto para devolver un FileStream:

/// <summary>
/// Creates a new Excel spreadsheet based on a template using the NPOI library.
/// The template is changed in memory and a copy of it is sent to
/// the user computer through a file stream.
/// </summary>
/// <returns>Excel report</returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NPOICreate()
{
    try
    {
        // Opening the Excel template...
        FileStream fs =
            new FileStream(Server.MapPath(@"\Content\NPOITemplate.xls"), FileMode.Open, FileAccess.Read);

        // Getting the complete workbook...
        HSSFWorkbook templateWorkbook = new HSSFWorkbook(fs, true);

        // Getting the worksheet by its name...
        HSSFSheet sheet = templateWorkbook.GetSheet("Sheet1");

        // Getting the row... 0 is the first row.
        HSSFRow dataRow = sheet.GetRow(4);

        // Setting the value 77 at row 5 column 1
        dataRow.GetCell(0).SetCellValue(77);

        // Forcing formula recalculation...
        sheet.ForceFormulaRecalculation = true;

        MemoryStream ms = new MemoryStream();

        // Writing the workbook content to the FileStream...
        templateWorkbook.Write(ms);

        TempData["Message"] = "Excel report created successfully!";

        // Sending the server processed data back to the user computer...
        return File(ms.ToArray(), "application/vnd.ms-excel", "NPOINewFile.xls");
    }
    catch(Exception ex)
    {
        TempData["Message"] = "Oops! Something went wrong.";

        return RedirectToAction("NPOI");
    }
}
Leniel Maccaferri
fuente
1

Aunque los resultados de la acción estándar FileContentResult o FileStreamResult se pueden utilizar para descargar archivos, la mejor solución podría ser crear un resultado de acción personalizado para su reutilización.

Como ejemplo, creemos un resultado de acción personalizado para exportar datos a archivos de Excel sobre la marcha para su descarga.

La clase ExcelResult hereda la clase ActionResult abstracta y anula el método ExecuteResult.

Estamos usando el paquete FastMember para crear DataTable a partir del objeto IEnumerable y el paquete ClosedXML para crear un archivo de Excel a partir de DataTable.

public class ExcelResult<T> : ActionResult
{
    private DataTable dataTable;
    private string fileName;

    public ExcelResult(IEnumerable<T> data, string filename, string[] columns)
    {
        this.dataTable = new DataTable();
        using (var reader = ObjectReader.Create(data, columns))
        {
            dataTable.Load(reader);
        }
        this.fileName = filename;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context != null)
        {
            var response = context.HttpContext.Response;
            response.Clear();
            response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            response.AddHeader("content-disposition", string.Format(@"attachment;filename=""{0}""", fileName));
            using (XLWorkbook wb = new XLWorkbook())
            {
                wb.Worksheets.Add(dataTable, "Sheet1");
                using (MemoryStream stream = new MemoryStream())
                {
                    wb.SaveAs(stream);
                    response.BinaryWrite(stream.ToArray());
                }
            }
        }
    }
}

En el controlador, use el resultado personalizado de la acción ExcelResult de la siguiente manera

[HttpGet]
public async Task<ExcelResult<MyViewModel>> ExportToExcel()
{
    var model = new Models.MyDataModel();
    var items = await model.GetItems();
    string[] columns = new string[] { "Column1", "Column2", "Column3" };
    string filename = "mydata.xlsx";
    return new ExcelResult<MyViewModel>(items, filename, columns);
}

Dado que estamos descargando el archivo usando HttpGet, cree una Vista vacía sin modelo y diseño vacío.

Publicación de blog sobre el resultado de una acción personalizada para descargar archivos que se crean sobre la marcha:

https://acanozturk.blogspot.com/2019/03/custom-actionresult-for-files-in-aspnet.html

Ahmetcan Ozturk
fuente
-4

Use el tipo de archivo .ashx y use el mismo código

0100110010101
fuente