如何显示要从MVC控制器下载的文件?


109

在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中完成相同的任务?

Answers:


181

根据您的操作是否返回a FileResultFileStreamResult,取决于文件是否存在或您即时创建文件。

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}

14
这是ASP.NET MVC为何如此出色的一个很好的例子。您以前必须在9行令人困惑的代码中执行的操作可以在一行中完成。这么简单!
乔恩·克鲁格

感谢tvanfosson,我为此寻求了最佳解决方案,这很棒。
Mark Kadlec

1
这需要文件名上的文件扩展名,否则它将完全忽略文件名和内容类型,而只是尝试将文件流式传输到浏览器。如果浏览器在强制下载时无法识别内容类型(即八位字节流),则它也将仅使用网页名称,并且根本没有扩展名。
RichC

62

要强制下载PDF文件,而不是由浏览器的PDF插件处理:

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

如果要让浏览器以默认行为(插件或下载)处理,只需发送两个参数即可。

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

您需要使用第三个参数在浏览器对话框上为文件指定名称。

更新:Charlino是正确的,当传递第三个参数(下载文件名)时,会将其Content-Disposition: attachment;添加到Http响应标头中。我的解决方案是以application\force-downloadmime-type的形式发送,但这会导致下载文件名出现问题,因此需要第三个参数来发送良好的文件名,因此无需强制执行下载


6
从技术上讲,这不是正在发生的事情。从技术上讲,当您添加第三个参数时,MVC框架会添加标头content-disposition: attachment; filename=MyRenamedFile.pdf-这就是强制下载的原因。我建议您将MIME类型放回application/pdf
Charlino'2

2
谢谢Charlino,我没有意识到第三个参数是这样做的,我认为这只是更改文件名。
guzart 2010年

2
+1用于更新您的答案并解释第三个参数+ Content-Disposition: attachment;关系。
Charlino'2

7

您可以在Razor或Controller中执行相同的操作。

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

或在控制器中。

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

    return View();
}

我在Chrome和IE9中都尝试过,两者都在下载pdf文件。

我可能应该补充一点,我正在使用RazorPDF生成我的PDF。这是关于它的博客:http : //nyveldt.com/blog/post/Introducing-RazorPDF


4

您应该查看Controller的File方法。这正是它的目的。它返回FilePathResult而不是ActionResult。


3

镁诺南

您可以执行以下操作以返回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");
    }
}

1

尽管可以将标准操作结果FileContentResult或FileStreamResult用于下载文件,但为了可重用,创建自定义操作结果可能是最好的解决方案。

作为示例,让我们创建一个自定义操作结果,以将数据即时导出到Excel文件进​​行下载。

ExcelResult类继承抽象的ActionResult类,并重写ExecuteResult方法。

我们正在使用FastMember包从IEnumerable对象创建DataTable,并使用ClosedXML包从DataTable创建Excel文件。

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

在Controller中,使用自定义ExcelResult操作结果,如下所示

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

由于我们使用HttpGet下载文件,因此创建一个没有模型和布局的空视图。

有关下载动态创建的文件的自定义操作结果的博客文章:

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.