Come posso presentare un file per il download da un controller MVC?


109

In WebForms, normalmente avrei un codice come questo per consentire al browser di presentare un popup "Scarica file" con un tipo di file arbitrario, come un PDF, e un nome file:

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

Come si esegue la stessa attività in ASP.NET MVC?

Risposte:


181

Restituisci un FileResulto FileStreamResultdalla tua azione, a seconda che il file esista o lo crei al volo.

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

14
Questo è un ottimo esempio del motivo per cui ASP.NET MVC è fantastico. Quello che dovevi fare in precedenza in 9 righe di codice dall'aspetto confuso può essere fatto in una riga. Molto più facile!
Jon Kruger

Grazie tvanfosson, ho cercato la soluzione migliore per farlo, e questo è fantastico.
Mark Kadlec

1
Ciò richiede un'estensione del file sul nome del file o altrimenti ignorerà completamente il nome del file e il tipo di contenuto e proverà semplicemente a trasmettere il file al browser. Utilizzerà anche il nome della pagina web se il browser non riconosce il tipo di contenuto (cioè il flusso di ottetti) quando forza il download e non avrà alcuna estensione.
RichC

62

Per forzare il download di un file PDF, invece di essere gestito dal plugin PDF del browser:

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

Se desideri che il browser gestisca il comportamento predefinito (plug-in o download), invia semplicemente due parametri.

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

Dovrai utilizzare il terzo parametro per specificare un nome per il file nella finestra di dialogo del browser.

AGGIORNAMENTO: Charlino ha ragione, quando si passa il terzo parametro (download filename) Content-Disposition: attachment;viene aggiunto all'Http Response Header. La mia soluzione era inviare application\force-downloadcome tipo mime, ma questo genera un problema con il nome del file del download, quindi il terzo parametro è necessario per inviare un buon nome del file, eliminando quindi la necessità di forzare un download .


6
Tecnicamente non è quello che sta succedendo. Tecnicamente quando aggiungi il terzo parametro, il framework MVC aggiunge l'intestazione content-disposition: attachment; filename=MyRenamedFile.pdf: questo è ciò che forza il download. Ti suggerirei di rimettere il tipo MIME in application/pdf.
Charlino

2
Grazie Charlino, non mi ero reso conto che il terzo parametro lo stava facendo, pensavo fosse solo per cambiare il nome del file.
guzart

2
+1 per aggiornare la tua risposta e spiegare il terzo parametro + Content-Disposition: attachment;relazione.
Charlino

7

Puoi fare lo stesso in Razor o nel controller, in questo modo ..

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

O nel controller ..

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

    return View();
}

Ho provato questo in Chrome e IE9, entrambi stanno scaricando il file pdf.

Probabilmente dovrei aggiungere che sto usando RazorPDF per generare i miei PDF. Ecco un blog a riguardo: http://nyveldt.com/blog/post/Introducing-RazorPDF


4

Dovresti guardare il metodo File del controller. Questo è esattamente quello che serve. Restituisce un FilePathResult invece di un ActionResult.


3

mgnoonan,

Puoi farlo per restituire un 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

Sebbene i risultati dell'azione standard FileContentResult o FileStreamResult possano essere utilizzati per scaricare i file, per la riutilizzabilità, la creazione di un risultato dell'azione personalizzata potrebbe essere la soluzione migliore.

Ad esempio, creiamo un risultato di azione personalizzato per esportare i dati in file Excel al volo per il download.

La classe ExcelResult eredita la classe ActionResult astratta e sostituisce il metodo ExecuteResult.

Stiamo utilizzando il pacchetto FastMember per la creazione di DataTable dall'oggetto IEnumerable e il pacchetto ClosedXML per la creazione di file Excel da 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());
                }
            }
        }
    }
}

Nel controller utilizzare il risultato dell'azione ExcelResult personalizzato come segue

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

Poiché stiamo scaricando il file utilizzando HttpGet, crea una vista vuota senza modello e layout vuoto.

Post del blog sul risultato dell'azione personalizzata per il download di file creati al volo:

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


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.