110

In WebForms, I would normally have code like this to let the browser present a "Download File" popup with an arbitrary file type, like a PDF, and a filename:

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()

How do I accomplish the same task in ASP.NET MVC?

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
mgnoonan
  • 7,060
  • 5
  • 24
  • 27

7 Answers7

184

Return a FileResult or FileStreamResult from your action, depending on whether the file exists or you create it on the fly.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
Bakudan
  • 19,134
  • 9
  • 53
  • 73
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 1
    This requires a file extension on the filename or otherwise it will completely ignore the filename and contenttype and just try to stream the file to the browser. It will also just use the webpage name if the browser doesn't recognize the contenttype (i.e. octet-stream) when it forces the download and it will not have an extension at all. – RichC Mar 24 '13 at 12:03
63

To force the download of a PDF file, instead of being handled by the browser's PDF plugin:

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

If you want to let the browser handle by its default behavior (plugin or download), just send two parameters.

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

You'll need to use the third parameter to specify a name for the file on the browser dialog.

UPDATE: Charlino is right, when passing the third parameter (download filename) Content-Disposition: attachment; gets added to the Http Response Header. My solution was to send application\force-download as the mime-type, but this generates a problem with the filename of the download so the third parameter is required to send a good filename, therefore eliminating the need to force a download.

meJustAndrew
  • 6,011
  • 8
  • 50
  • 76
guzart
  • 3,700
  • 28
  • 23
  • 6
    Technically that's not what is happening. Technically when you add the third parameter, the MVC framework adds the header `content-disposition: attachment; filename=MyRenamedFile.pdf` - this is what forces the download. I would suggest you put the MIME type back to `application/pdf`. – Charlino Feb 01 '10 at 08:01
  • 2
    Thank you Charlino, I didn't realized the third parameter was doing that, I thought it was just to change the filename. – guzart Feb 01 '10 at 21:25
8

You can do the same in Razor or in the Controller, like so..

@{
    //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");
}

Or in the Controller..

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

    return View();
}

I tried this in Chrome and IE9, both is downloading the pdf file.

I probably should add I am using RazorPDF to generate my PDFs. Here is a blog about it: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
  • 24,267
  • 23
  • 130
  • 154
4

You should look at the File method of the Controller. This is exactly what it's for. It returns a FilePathResult instead of an ActionResult.

Martin Peck
  • 11,440
  • 2
  • 42
  • 69
3

mgnoonan,

You can do this to return a 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
  • 100,159
  • 46
  • 371
  • 480
1

Although standard action results FileContentResult or FileStreamResult may be used for downloading files, for reusability, creating a custom action result might be the best solution.

As an example let's create a custom action result for exporting data to Excel files on the fly for download.

ExcelResult class inherits abstract ActionResult class and overrides the ExecuteResult method.

We are using FastMember package for creating DataTable from IEnumerable object and ClosedXML package for creating Excel file from the 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());
                }
            }
        }
    }
}

In the Controller use the custom ExcelResult action result as follows

[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);
}

Since we are downloading the file using HttpGet, create an empty View without model and empty layout.

Blog post about custom action result for downloading files that are created on the fly:

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

-4

Use .ashx file type and use the same code

0100110010101
  • 6,469
  • 5
  • 33
  • 38