Comment puis-je présenter un fichier à télécharger à partir d'un contrôleur MVC?

109

Dans WebForms, j'aurais normalement un code comme celui-ci pour permettre au navigateur de présenter une fenêtre contextuelle "Télécharger le fichier" avec un type de fichier arbitraire, comme un PDF, et un nom de fichier:

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

Comment puis-je accomplir la même tâche dans ASP.NET MVC?

mgnoonan
la source

Réponses:

181

Renvoyez un FileResultou FileStreamResultde votre action, selon que le fichier existe ou que vous le créez à la volée.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
Tvanfosson
la source
14
Ceci est un excellent exemple de la raison pour laquelle ASP.NET MVC est génial. Ce que vous deviez faire auparavant en 9 lignes de code déroutant peut être fait en une seule ligne. Tellement plus facile!
Jon Kruger
Merci tvanfosson, j'ai cherché la meilleure solution pour ce faire, et c'est génial.
Mark Kadlec
1
Cela nécessite une extension de fichier sur le nom de fichier ou sinon il ignorera complètement le nom de fichier et le type de contenu et essaiera simplement de diffuser le fichier dans le navigateur. Il utilisera également simplement le nom de la page Web si le navigateur ne reconnaît pas le type de contenu (c'est-à-dire le flux d'octets) lorsqu'il force le téléchargement et il n'aura pas du tout d'extension.
RichC
62

Pour forcer le téléchargement d'un fichier PDF, au lieu d'être manipulé par le plugin PDF du navigateur:

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

Si vous souhaitez laisser le navigateur gérer son comportement par défaut (plugin ou téléchargement), envoyez simplement deux paramètres.

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

Vous devrez utiliser le troisième paramètre pour spécifier un nom pour le fichier dans la boîte de dialogue du navigateur.

MISE À JOUR: Charlino a raison, lors du passage du troisième paramètre (télécharger le nom du fichier) Content-Disposition: attachment;est ajouté à l'en-tête de réponse Http. Ma solution était d'envoyer en application\force-downloadtant que type mime, mais cela génère un problème avec le nom de fichier du téléchargement, donc le troisième paramètre est nécessaire pour envoyer un bon nom de fichier, éliminant ainsi le besoin de forcer un téléchargement .

guzart
la source
6
Techniquement, ce n'est pas ce qui se passe. Techniquement, lorsque vous ajoutez le troisième paramètre, le framework MVC ajoute l'en-tête content-disposition: attachment; filename=MyRenamedFile.pdf- c'est ce qui force le téléchargement. Je vous suggère de remettre le type MIME à application/pdf.
Charlino
2
Merci Charlino, je n'ai pas réalisé que le troisième paramètre faisait ça, je pensais que c'était juste pour changer le nom du fichier.
guzart
2
+1 pour mettre à jour votre réponse et expliquer le troisième paramètre + la Content-Disposition: attachment;relation.
Charlino
7

Vous pouvez faire la même chose dans Razor ou dans le contrôleur, comme ça.

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

Ou dans le contrôleur.

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

    return View();
}

J'ai essayé cela dans Chrome et IE9, les deux téléchargeant le fichier pdf.

Je devrais probablement ajouter que j'utilise RazorPDF pour générer mes PDF. Voici un blog à ce sujet: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
la source
4

Vous devriez regarder la méthode File du Controller. C'est exactement ce que c'est. Il renvoie un FilePathResult au lieu d'un ActionResult.

Martin Peck
la source
3

mgnoonan,

Vous pouvez faire ceci pour renvoyer 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");
    }
}
Leniel Maccaferri
la source
1

Bien que les résultats d'action standard FileContentResult ou FileStreamResult puissent être utilisés pour télécharger des fichiers, pour la réutilisation, la création d'un résultat d'action personnalisé peut être la meilleure solution.

À titre d'exemple, créons un résultat d'action personnalisé pour l'exportation de données vers des fichiers Excel à la volée pour téléchargement.

La classe ExcelResult hérite de la classe ActionResult abstraite et remplace la méthode ExecuteResult.

Nous utilisons le package FastMember pour créer DataTable à partir de l'objet IEnumerable et le package ClosedXML pour créer un fichier Excel à partir de 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());
                }
            }
        }
    }
}

Dans le contrôleur, utilisez le résultat de l'action ExcelResult personnalisé comme suit

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

Puisque nous téléchargeons le fichier à l'aide de HttpGet, créez une vue vide sans modèle et une mise en page vide.

Article de blog sur le résultat de l'action personnalisée pour le téléchargement de fichiers créés à la volée:

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

Ahmetcan Ozturk
la source
-4

Utilisez le type de fichier .ashx et utilisez le même code

0100110010101
la source