Datei eines beliebigen Typs in Asp.Net MVC mit FileResult herunterladen?


228

Ich habe mir vorgeschlagen, FileResult zu verwenden, damit Benutzer Dateien von meiner Asp.Net MVC-Anwendung herunterladen können. Aber die einzigen Beispiele dafür, die ich finden kann, haben immer mit Bilddateien zu tun (Angabe des Inhaltstyps image / jpeg).

Aber was ist, wenn ich den Dateityp nicht kennen kann? Ich möchte, dass Benutzer so gut wie jede Datei aus dem Dateibereich meiner Website herunterladen können.

Ich hatte eine Methode dazu gelesen (siehe einen vorherigen Beitrag für den Code), die tatsächlich gut funktioniert, abgesehen von einer Sache: Der Name der Datei, die im Dialogfeld Speichern unter angezeigt wird, wird aus dem Dateipfad mit Unterstrichen verkettet ( folder_folder_file.ext). Es scheint auch, dass die Leute denken, ich sollte ein FileResult zurückgeben, anstatt diese benutzerdefinierte Klasse zu verwenden, die ich BinaryContentResult gefunden hatte.

Kennt jemand die "richtige" Art, einen solchen Download in MVC durchzuführen?

EDIT: Ich habe die Antwort (unten) erhalten, dachte aber nur, ich sollte den vollständigen Arbeitscode posten, wenn jemand anderes interessiert ist:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}

12
Was Sie tun, ist ziemlich gefährlich. Sie erlauben Benutzern so ziemlich, jede Datei von Ihrem Server herunterzuladen, auf die der ausführende Benutzer zugreifen kann.
Paul Fleming

1
Richtig - das Entfernen des Dateipfads und das Festnageln im Hauptteil des Aktionsergebnisses wäre etwas sicherer. Zumindest haben sie so nur Zugriff auf einen bestimmten Ordner.
Shubniggurath

2
Gibt es Tools, mit denen Sie potenziell gefährliche Lücken wie diese finden können?
David

Ich finde, dass es bequem ist, den Inhaltstyp als Response.ContentType = MimeMapping.GetMimeMapping(filePath);von stackoverflow.com/a/22231074/4573839
yu yang Jian

Was verwenden Sie auf Client-Seite?
FrenkyB

Antworten:


425

Sie können einfach den generischen Octet-Stream-MIME-Typ angeben:

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

4
Ok, ich könnte das versuchen, aber was geht in das Byte [] Array?
Anders

3
Egal, ich glaube ich habe es herausgefunden. Ich habe den Dateinamen (vollständiger Pfad) in einen FileStream und dann in ein Byte-Array gelesen und dann hat es wie ein Zauber funktioniert! Vielen Dank!
Anders

5
Dadurch wird die gesamte Datei in den Speicher geladen, um sie zu streamen. Für große Dateien ist dies ein Schwein. Eine viel bessere Lösung ist die folgende, bei der die Datei nicht zuerst in den Speicher geladen werden muss.
HBlackorby

13
Da diese Antwort fast fünf Jahre alt ist, ja. Wenn Sie dies tun, um sehr große Dateien bereitzustellen, tun Sie dies nicht. Verwenden Sie nach Möglichkeit einen separaten statischen Dateiserver, damit Sie Ihre Anwendungsthreads nicht binden, oder eine der vielen neuen Techniken zum Bereitstellen von Dateien, die MVC seit 2010 hinzugefügt wurden. Dies zeigt nur den richtigen MIME-Typ an, der verwendet werden soll, wenn der MIME-Typ unbekannt ist . ReadAllByteswurde Jahre später in einer Bearbeitung hinzugefügt. Warum ist dies meine zweithäufigste Antwort? Naja.
Ian Henry

10
Diesen Fehler erhalten:non-invocable member "File" cannot be used like a method.
A-Sharabiani

105

Das MVC-Framework unterstützt dies nativ. Der Controller System.Web.MVC.Controller.File bietet Methoden zum Zurückgeben einer Datei nach Name / Stream / Array .

Wenn Sie beispielsweise einen virtuellen Pfad zur Datei verwenden, können Sie Folgendes tun.

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));

36

Wenn Sie .NET Framework 4.5 verwenden, verwenden Sie MimeMapping.GetMimeMapping (Zeichenfolge FileName), um den MIME-Typ für Ihre Datei abzurufen. So habe ich es in meiner Aktion verwendet.

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);

Das Get Mime-Mapping ist nett, aber ist es nicht ein schwerer Prozess, um herauszufinden, welcher Dateityp zur Laufzeit verwendet wird?
Mohammed Noureldin

@MohammedNoureldin es ist nicht "herauszufinden", es gibt eine einfache Zuordnungstabelle, die auf Dateierweiterungen oder ähnlichem basiert. Der Server macht das für alle statischen Dateien, es ist nicht langsam.
Al Kepp

13

Phil Haack hat einen schönen Artikel, in dem er eine Ergebnisklasse für benutzerdefinierte Dateidownload-Aktionen erstellt hat. Sie müssen nur den virtuellen Pfad der Datei und den Namen angeben, unter dem gespeichert werden soll.

Ich habe es einmal benutzt und hier ist mein Code.

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

In meinem Beispiel habe ich den physischen Pfad der Dateien gespeichert, also habe ich diese Hilfsmethode verwendet, die ich an einem Ort gefunden habe, an den ich mich nicht erinnern kann, um sie in einen virtuellen Pfad zu konvertieren

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

Hier ist die ganze Klasse aus Phill Haacks Artikel

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}

1
Richtig, ja, ich habe diesen Artikel auch gesehen, aber er scheint ungefähr das Gleiche zu tun wie der Artikel, den ich verwendet habe (siehe den Verweis auf meinen vorherigen Beitrag), und er sagt sich oben auf der Seite, dass die Problemumgehung nicht erfolgen sollte. Wird nicht mehr benötigt, weil: "NEUES UPDATE: Dieses benutzerdefinierte Aktionsergebnis wird nicht mehr benötigt, da ASP.NET MVC jetzt eines im Lieferumfang enthält." Leider sagt er nichts anderes darüber, wie dies verwendet werden soll.
Anders

@ManafAbuRous, wenn Sie den Code genau lesen, werden Sie sehen, dass er den virtuellen Pfad tatsächlich in den physischen Pfad ( Server.MapPath(this.VirtualPath)) konvertiert. Daher ist es ein bisschen naiv, dies direkt ohne Änderung zu konsumieren. Sie sollten eine Alternative erstellen, die akzeptiert PhysicalPath, vorausgesetzt, dass dies letztendlich erforderlich ist und gespeichert wird. Dies wäre viel sicherer, da Sie davon ausgegangen sind, dass der physische Pfad und der relative Pfad identisch sind (ohne die Wurzel). Datendateien werden häufig in App_Data gespeichert. Dies ist als relativer Pfad nicht zugänglich.
Paul Fleming

GetVirtualPath ist großartig ... sehr nützlich. Danke!
Zvi Redler

6

Vielen Dank an Ian Henry !

Falls Sie eine Datei von MS SQL Server benötigen , finden Sie hier die Lösung.

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

Wo AppModel ist EntityFrameworkModell und MyFiles präsentiert Tabelle in der Datenbank. FileData befindet sich varbinary(MAX)in der MyFiles- Tabelle.


2

Geben Sie einfach Ihren physischen Pfad in directoryPath mit dem Dateinamen an

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}

Was ist mit der Client-Seite, die diese Methode aufruft? Sagen wir mal, ob Sie Speichern als Dialog anzeigen möchten?
FrenkyB

0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }

-1

if (string.IsNullOrWhiteSpace (fileName)) gibt Content zurück ("Dateiname nicht vorhanden");

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);

-4

GetFile sollte die Datei schließen (oder innerhalb einer using öffnen). Anschließend können Sie die Datei nach der Konvertierung in Bytes löschen. Der Download erfolgt in diesem Bytepuffer.

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

Also in Ihrer Download-Methode ...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));

2
Bitte laden Sie niemals ganze Dateien in den Speicher in die Produktion wie
folgt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.