Wie kann ich eine Datei zum Herunterladen von einem MVC-Controller präsentieren?

109

In WebForms hätte ich normalerweise Code wie diesen, damit der Browser ein Popup "Datei herunterladen" mit einem beliebigen Dateityp wie einer PDF-Datei und einem Dateinamen anzeigt:

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

Wie führe ich dieselbe Aufgabe in ASP.NET MVC aus?

mgnoonan
quelle

Antworten:

181

Geben Sie ein FileResultoder FileStreamResultaus Ihrer Aktion zurück, je nachdem, ob die Datei vorhanden ist oder Sie sie im laufenden Betrieb erstellen.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
Tvanfosson
quelle
14
Dies ist ein großartiges Beispiel dafür, warum ASP.NET MVC großartig ist. Was Sie zuvor in 9 Zeilen verwirrend aussehenden Codes tun mussten, kann in einer Zeile erledigt werden. So viel einfacher!
Jon Kruger
Danke tvanfosson, ich habe nach der besten Lösung dafür gesucht, und das ist großartig.
Mark Kadlec
1
Dies erfordert eine Dateierweiterung des Dateinamens. Andernfalls werden der Dateiname und der Inhaltstyp vollständig ignoriert und es wird lediglich versucht, die Datei an den Browser zu streamen. Es wird auch nur der Name der Webseite verwendet, wenn der Browser den Inhaltstyp (dh den Oktett-Stream) nicht erkennt, wenn er den Download erzwingt, und überhaupt keine Erweiterung hat.
RichC
62

So erzwingen Sie das Herunterladen einer PDF-Datei, anstatt vom PDF-Plugin des Browsers verarbeitet zu werden:

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

Wenn Sie möchten, dass der Browser mit seinem Standardverhalten (Plugin oder Download) umgeht, senden Sie einfach zwei Parameter.

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

Sie müssen den dritten Parameter verwenden, um einen Namen für die Datei im Browser-Dialogfeld anzugeben.

UPDATE: Charlino ist richtig, wenn der dritte Parameter (Download-Dateiname) Content-Disposition: attachment;zum HTTP-Antwortheader hinzugefügt wird. Meine Lösung bestand darin, application\force-downloadals MIME-Typ zu senden. Dies führt jedoch zu einem Problem mit dem Dateinamen des Downloads, sodass der dritte Parameter zum Senden eines guten Dateinamens erforderlich ist, sodass kein Download erzwungen werden muss .

Guzart
quelle
6
Technisch gesehen passiert das nicht. Wenn Sie den dritten Parameter hinzufügen, fügt das MVC-Framework den Header hinzu content-disposition: attachment; filename=MyRenamedFile.pdf- dies erzwingt den Download. Ich würde vorschlagen, dass Sie den MIME-Typ zurücksetzen application/pdf.
Charlino
2
Vielen Dank, Charlino. Ich habe nicht bemerkt, dass der dritte Parameter dies tut. Ich dachte, es geht nur darum, den Dateinamen zu ändern.
Guzart
2
+1 für die Aktualisierung Ihrer Antwort und die Erläuterung des dritten Parameters + Content-Disposition: attachment;Beziehung.
Charlino
7

Sie können dasselbe in Razor oder im Controller tun.

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

Oder im Controller ..

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

    return View();
}

Ich habe dies in Chrome und IE9 versucht, beide laden die PDF-Datei herunter.

Ich sollte wahrscheinlich hinzufügen, dass ich RazorPDF verwende , um meine PDFs zu generieren. Hier ist ein Blog darüber: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
quelle
4

Sie sollten sich die File-Methode des Controllers ansehen. Genau dafür ist es. Es gibt ein FilePathResult anstelle eines ActionResult zurück.

Martin Peck
quelle
3

mgnoonan,

Sie können dies tun, um einen FileStream zurückzugeben:

/// <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
quelle
1

Obwohl Standardaktionsergebnisse FileContentResult oder FileStreamResult zum Herunterladen von Dateien verwendet werden können, ist das Erstellen eines benutzerdefinierten Aktionsergebnisses aus Gründen der Wiederverwendbarkeit möglicherweise die beste Lösung.

Als Beispiel erstellen wir ein benutzerdefiniertes Aktionsergebnis zum sofortigen Exportieren von Daten in Excel-Dateien zum Herunterladen.

Die ExcelResult-Klasse erbt die abstrakte ActionResult-Klasse und überschreibt die ExecuteResult-Methode.

Wir verwenden das FastMember-Paket zum Erstellen von DataTable aus einem IEnumerable-Objekt und das ClosedXML-Paket zum Erstellen einer Excel-Datei aus der 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());
                }
            }
        }
    }
}

Verwenden Sie im Controller das Ergebnis der benutzerdefinierten ExcelResult-Aktion wie folgt

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

Da wir die Datei mit HttpGet herunterladen, erstellen Sie eine leere Ansicht ohne Modell und leeres Layout.

Blogbeitrag über benutzerdefinierte Aktionsergebnisse zum Herunterladen von Dateien, die im laufenden Betrieb erstellt wurden:

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

Ahmetcan Ozturk
quelle
-4

Verwenden Sie den Dateityp .ashx und denselben Code

0100110010101
quelle