如何从 MVC 控制器下载文件?

在 WebForms 中,我通常会有这样的代码,让浏览器显示一个带有任意文件类型(比如 PDF)和文件名的“下载文件”弹出窗口:

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

如何在 ASP.NET MVC 中完成同样的任务?

115096 次浏览

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.

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

Use .ashx file type and use the same code

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

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.

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

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