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;
}
Anders
quelle
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);
}
Ian Henry
quelle
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));
Jonathan
quelle
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);
Salman Hasrat Khan
quelle
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);
    }
}
Manaf Abu.Rous
quelle
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.

Entwickler
quelle
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);
}
DARSHAN SHINDE
quelle
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);
        }
Hossein Zakizadeh
quelle
-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);
Caio Augusto
quelle
-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));
CDichter
quelle
2
Bitte laden Sie niemals ganze Dateien in den Speicher in die Produktion wie
folgt