Scarica file di qualsiasi tipo in Asp.Net MVC usando FileResult?


228

Mi è stato suggerito di utilizzare FileResult per consentire agli utenti di scaricare file dalla mia applicazione MVC Asp.Net. Ma gli unici esempi che posso trovare hanno sempre a che fare con i file di immagine (specificando il tipo di contenuto image / jpeg).

E se non sapessi il tipo di file? Voglio che gli utenti siano in grado di scaricare praticamente qualsiasi file dall'archivio del mio sito.

Avevo letto un metodo per farlo (vedi un post precedente per il codice), che funziona davvero bene, tranne una cosa: il nome del file che appare nella finestra di dialogo Salva con nome è concatenato dal percorso del file con caratteri di sottolineatura ( folder_folder_file.ext). Inoltre, sembra che la gente pensi che dovrei restituire un FileResult invece di usare questa classe personalizzata che avevo trovato BinaryContentResult.

Qualcuno conosce il modo "corretto" di fare un tale download in MVC?

EDIT: ho ricevuto la risposta (sotto), ma ho pensato di pubblicare il codice di lavoro completo se qualcun altro è interessato:

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
Quello che stai facendo è piuttosto pericoloso. Stai praticamente permettendo agli utenti di scaricare qualsiasi file dal tuo server a cui l'utente che esegue l'esecuzione può accedere.
Paul Fleming,

1
Vero: rimuovere il percorso del file e inchiodarlo nel corpo del risultato dell'azione sarebbe in qualche modo più sicuro. Almeno in questo modo hanno accesso solo a una determinata cartella.
shubniggurath,

2
Esistono strumenti che ti consentono di trovare scappatoie potenzialmente pericolose come questa?
David,

Trovo che sia conveniente impostare il tipo di contenuto come Response.ContentType = MimeMapping.GetMimeMapping(filePath);, da stackoverflow.com/a/22231074/4573839
yu yang Jian,

Cosa stai usando sul lato client?
FrenkyB,

Risposte:


425

Puoi semplicemente specificare il tipo MIME generico di ottetti-stream:

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, potrei provarlo, ma cosa va nell'array byte []?
Anders,

3
Non importa, penso di averlo capito. Ho letto il nome del file (percorso completo) in un FileStream e poi in un array di byte, e poi ha funzionato come un incantesimo! Grazie!
Anders,

5
Questo carica l'intero file in memoria solo per lo streaming; per file di grandi dimensioni, questo è un maiale. Una soluzione molto migliore è quella di seguito che non deve prima caricare il file in memoria.
HBlackorby,

13
Dato che questa risposta ha quasi cinque anni, sì. Se lo stai facendo per servire file molto grandi, non farlo. Se possibile, utilizzare un file server statico separato in modo da non legare i thread dell'applicazione o una delle molte nuove tecniche per la pubblicazione di file aggiunti a MVC dal 2010. Questo mostra solo il tipo MIME corretto da utilizzare quando il tipo MIME è sconosciuto . ReadAllBytesè stato aggiunto anni dopo in una modifica. Perché questa è la mia seconda risposta più votata? Oh bene.
Ian Henry,

10
Ottenere questo errore: non-invocable member "File" cannot be used like a method.
A-Sharabiani,

105

Il framework MVC supporta questo in modo nativo. Il controller System.Web.MVC.Controller.File fornisce metodi per restituire un file per nome / flusso / matrice .

Ad esempio, utilizzando un percorso virtuale per il file, è possibile effettuare le seguenti operazioni.

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

36

Se si utilizza .NET Framework 4.5, utilizzare MimeMapping.GetMimeMapping (nome file stringa) per ottenere il tipo MIME per il file. È così che l'ho usato nella mia azione.

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

Ottenere la mappatura di Mime è bello, ma non è un processo pesante per capire qual è il tipo di file in fase di esecuzione?
Mohammed Noureldin,

@MohammedNoureldin non lo sta "immaginando", esiste una semplice tabella di mappatura basata su estensioni di file o qualcosa del genere. Il server lo fa per tutti i file statici, non è lento.
Al Kepp,

13

Phil Haack ha un bell'articolo in cui ha creato una classe di risultati dell'azione di download di file personalizzati. Devi solo specificare il percorso virtuale del file e il nome da salvare come.

L'ho usato una volta ed ecco il mio codice.

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

Nel mio esempio stavo memorizzando il percorso fisico dei file, quindi ho usato questo metodo di supporto - che ho trovato da qualche parte che non ricordo - per convertirlo in un percorso virtuale

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

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

            return "~/" + physicalPath;
        }

Ecco la lezione completa tratta dall'articolo di Phill Haack

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
Bene, sì, ho visto anche quell'articolo, ma sembra fare la stessa cosa dell'articolo che ho usato (vedi il riferimento al mio precedente post), e si dice in cima alla pagina che la soluzione alternativa non dovrebbe ' non sarà più necessario perché: "NUOVO AGGIORNAMENTO: non è più necessario per questo ActionResult personalizzato perché ASP.NET MVC ora ne include uno nella casella." Ma sfortunatamente, non dice nient'altro su come questo deve essere usato.
Anders,

@ManafAbuRous, se leggi attentamente il codice, vedrai che converte il percorso virtuale in percorso fisico ( Server.MapPath(this.VirtualPath)), quindi consumarlo direttamente senza modifiche è un po 'ingenuo. Dovresti produrre un supplente che accetti PhysicalPathdato che è ciò che è eventualmente richiesto ed è ciò che stai memorizzando. Questo sarebbe molto più sicuro in quanto hai ipotizzato che il percorso fisico e il percorso relativo sarebbero gli stessi (esclusa la radice). I file di dati vengono spesso archiviati in App_Data. Questo non è accessibile come percorso relativo.
Paul Fleming,

GetVirtualPath è fantastico .... molto utile. grazie!
Zvi Redler,

6

Grazie a Ian Henry !

Nel caso in cui sia necessario ottenere file da MS SQL Server, ecco la soluzione.

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

Dove AppModel è il EntityFrameworkmodello e MyFiles presenta la tabella nel database. FileData è varbinary(MAX)nella tabella MyFiles .


2

è semplice, basta dare il tuo percorso fisico in directoryPath con il nome del file

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

Che dire del lato client, chiamando questo metodo? Diciamo se si desidera mostrare Salva come finestra di dialogo?
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)) restituisce il contenuto ("nome file non presente");

        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 dovrebbe chiudere il file (o aprirlo in un utilizzo). Quindi è possibile eliminare il file dopo la conversione in byte: il download verrà eseguito su quel buffer di byte.

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

Quindi nel tuo metodo di download ...

        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
Per favore, non caricare mai interi file nella memoria in produzione in questo modo
makhdumi,
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.