Jak mogę przedstawić plik do pobrania z kontrolera MVC?

109

W formularzach internetowych zwykle miałbym taki kod, aby przeglądarka wyświetlała wyskakujące okienko „Pobierz plik” z dowolnym typem pliku, takim jak PDF, i nazwą pliku:

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

Jak mogę wykonać to samo zadanie w ASP.NET MVC?

mgnoonan
źródło

Odpowiedzi:

181

Zwróć FileResultlub FileStreamResultz akcji, w zależności od tego, czy plik istnieje, czy tworzysz go w locie.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
tvanfosson
źródło
14
To doskonały przykład, dlaczego ASP.NET MVC jest niesamowity. To, co wcześniej musiałeś zrobić w 9 wierszach myląco wyglądającego kodu, można zrobić w jednym wierszu. O wiele łatwiejsze!
Jon Kruger
Dzięki tvanfosson, szukałem najlepszego rozwiązania, aby to zrobić, i to jest świetne.
Mark Kadlec,
1
Wymaga to rozszerzenia pliku w nazwie pliku, w przeciwnym razie całkowicie zignoruje nazwę pliku i typ zawartości i po prostu spróbuje przesłać plik strumieniowo do przeglądarki. Po prostu użyje nazwy strony internetowej, jeśli przeglądarka nie rozpozna typu zawartości (tj. Strumienia oktetu) podczas wymuszania pobierania i nie będzie miała w ogóle rozszerzenia.
RichC,
62

Aby wymusić pobranie pliku PDF zamiast obsługiwać go przez wtyczkę PDF przeglądarki:

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

Jeśli chcesz, aby przeglądarka działała zgodnie z domyślnym zachowaniem (wtyczka lub pobieranie), po prostu wyślij dwa parametry.

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

Musisz użyć trzeciego parametru, aby określić nazwę pliku w oknie dialogowym przeglądarki.

AKTUALIZACJA: Charlino ma rację, po przekazaniu trzeciego parametru (nazwa pliku do pobrania) Content-Disposition: attachment;zostaje dodany do nagłówka odpowiedzi HTTP. Moim rozwiązaniem było wysłanie application\force-downloadjako typu MIME, ale generuje to problem z nazwą pliku do pobrania, więc trzeci parametr jest wymagany do przesłania dobrej nazwy pliku, co eliminuje potrzebę wymuszania pobierania .

guzart
źródło
6
Technicznie rzecz biorąc, tak się nie dzieje. Technicznie rzecz biorąc, po dodaniu trzeciego parametru struktura MVC dodaje nagłówek content-disposition: attachment; filename=MyRenamedFile.pdf- to wymusza pobieranie. Sugerowałbym przywrócenie typu MIME do application/pdf.
Charlino
2
Dziękuję Charlino, nie zdawałem sobie sprawy, że trzeci parametr to robi, myślałem, że chodzi tylko o zmianę nazwy pliku.
guzart
2
+1 za aktualizację odpowiedzi i wyjaśnienie trzeciego parametru + Content-Disposition: attachment;relacji.
Charlino
7

Możesz zrobić to samo w Razor lub w kontrolerze, na przykład.

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

Lub w kontrolerze ...

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

    return View();
}

Wypróbowałem to w Chrome i IE9, oba pobierają plik pdf.

Prawdopodobnie powinienem dodać, że używam RazorPDF do generowania moich plików PDF. Oto blog na ten temat: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
źródło
4

Powinieneś spojrzeć na metodę pliku kontrolera. Właśnie do tego służy. Zwraca FilePathResult zamiast ActionResult.

Martin Peck
źródło
3

mgnoonan,

Możesz to zrobić, aby zwrócić 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
źródło
1

Chociaż standardowe wyniki akcji FileContentResult lub FileStreamResult mogą być używane do pobierania plików, w celu ponownego wykorzystania najlepszym rozwiązaniem może być utworzenie wyniku akcji niestandardowej.

Jako przykład utwórzmy wynik akcji niestandardowej do eksportowania danych do plików programu Excel w locie w celu pobrania.

Klasa ExcelResult dziedziczy abstrakcyjną klasę ActionResult i przesłania metodę ExecuteResult.

Używamy pakietu FastMember do tworzenia DataTable z obiektu IEnumerable i pakietu ClosedXML do tworzenia pliku Excel z 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());
                }
            }
        }
    }
}

W kontrolerze użyj wyniku niestandardowej akcji ExcelResult w następujący sposób

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

Ponieważ pobieramy plik za pomocą HttpGet, utwórz pusty widok bez modelu i pustego układu.

Wpis na blogu dotyczący wyniku akcji niestandardowej do pobierania plików tworzonych w locie:

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

Ahmetcan Ozturk
źródło
-4

Użyj typu pliku .ashx i użyj tego samego kodu

0100110010101
źródło