Télécharger un fichier de n'importe quel type dans Asp.Net MVC en utilisant FileResult?


228

Je me suis fait suggérer d'utiliser FileResult pour permettre aux utilisateurs de télécharger des fichiers depuis mon application Asp.Net MVC. Mais les seuls exemples que je puisse trouver concernent toujours les fichiers image (en spécifiant le type de contenu image / jpeg).

Mais que faire si je ne connais pas le type de fichier? Je veux que les utilisateurs puissent télécharger à peu près n'importe quel fichier à partir de la zone de fichiers de mon site.

J'avais lu une méthode pour le faire (voir un article précédent pour le code), qui fonctionne vraiment bien, sauf pour une chose: le nom du fichier qui apparaît dans la boîte de dialogue Enregistrer sous est concaténé à partir du chemin d'accès au fichier avec des traits de soulignement ( folder_folder_file.ext). De plus, il semble que les gens pensent que je devrais retourner un FileResult au lieu d'utiliser cette classe personnalisée que j'avais trouvée BinaryContentResult.

Quelqu'un connaît-il la "bonne" façon de faire un tel téléchargement dans MVC?

EDIT: J'ai obtenu la réponse (ci-dessous), mais je pensais juste que je devrais publier le code de travail complet si quelqu'un d'autre est intéressé:

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
Ce que vous faites est plutôt dangereux. Vous autorisez à peu près les utilisateurs à télécharger n'importe quel fichier de votre serveur auquel l'utilisateur exécutant peut accéder.
Paul Fleming

1
Vrai - supprimer le chemin du fichier et le clouer dans le corps du résultat de l'action serait quelque peu plus sûr. Au moins de cette façon, ils n'ont accès qu'à un certain dossier.
shubniggurath

2
Existe-t-il des outils qui vous permettent de trouver des failles potentiellement dangereuses comme celle-ci?
David

Je trouve qu'il est pratique de définir le type de contenu comme Response.ContentType = MimeMapping.GetMimeMapping(filePath);, à partir de stackoverflow.com/a/22231074/4573839
yu yang Jian

Qu'utilisez-vous côté client?
FrenkyB

Réponses:


425

Vous pouvez simplement spécifier le type MIME générique d'octet-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, je pourrais essayer ça, mais qu'est-ce qui entre dans le tableau d'octets []?
Anders

3
Peu importe, je pense que je l'ai compris. J'ai lu le nom de fichier (chemin complet) dans un FileStream puis dans un tableau d'octets, puis cela a fonctionné comme un charme! Merci!
Anders

5
Cela charge le fichier entier en mémoire juste pour le diffuser; pour les gros fichiers, c'est un porc. Une bien meilleure solution est celle ci-dessous qui ne doit pas d'abord charger le fichier en mémoire.
HBlackorby

13
Comme cette réponse a presque cinq ans, oui. Si vous faites cela pour servir de très gros fichiers, ne le faites pas. Si possible, utilisez un serveur de fichiers statique distinct afin de ne pas bloquer vos threads d'application, ou l'une des nombreuses nouvelles techniques pour servir les fichiers ajoutés à MVC depuis 2010. Cela montre simplement le type MIME correct à utiliser lorsque le type MIME est inconnu . ReadAllBytesa été ajouté des années plus tard dans une édition. Pourquoi est-ce ma deuxième réponse la plus votée? Tant pis.
Ian Henry

10
Obtenir cette erreur:non-invocable member "File" cannot be used like a method.
A-Sharabiani

105

Le framework MVC le supporte nativement. Le contrôleur System.Web.MVC.Controller.File fournit des méthodes pour renvoyer un fichier par nom / flux / tableau .

Par exemple, en utilisant un chemin d'accès virtuel au fichier, vous pouvez effectuer les opérations suivantes.

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

36

Si vous utilisez .NET Framework 4.5, vous utilisez alors le MimeMapping.GetMimeMapping (chaîne FileName) pour obtenir le type MIME pour votre fichier. C'est ainsi que je l'ai utilisé dans mon action.

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

Ce mappage Mime est agréable, mais n'est-ce pas un processus accéléré pour déterminer quel est le type de fichier lors de l'exécution?
Mohammed Noureldin

@MohammedNoureldin ce n'est pas "le comprendre", il y a une table de mappage simple basée sur des extensions de fichier ou quelque chose comme ça. Le serveur le fait pour tous les fichiers statiques, ce n'est pas lent.
Al Kepp

13

Phil Haack a un bel article où il a créé une classe de résultat d'action de téléchargement de fichier personnalisé. Il vous suffit de spécifier le chemin virtuel du fichier et le nom sous lequel il sera enregistré.

Je l'ai utilisé une fois et voici mon 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 };
        }

Dans mon exemple, je stockais le chemin physique des fichiers, j'ai donc utilisé cette méthode d'assistance - que j'ai trouvée quelque part dont je ne me souviens pas - pour la convertir en chemin virtuel

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

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

            return "~/" + physicalPath;
        }

Voici la classe complète tirée de l'article de 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
Oui, j'ai vu cet article aussi, mais il semble faire en quelque sorte la même chose que l'article que j'ai utilisé (voir la référence à mon post précédent), et il se dit en haut de la page que la solution de contournement ne devrait pas '' t ne sera plus nécessaire car: "NOUVELLE MISE À JOUR: Il n'y a plus besoin de ce ActionResult personnalisé car ASP.NET MVC en inclut désormais un dans la boîte." Mais malheureusement, il ne dit rien d'autre sur la façon dont cela doit être utilisé.
Anders

@ManafAbuRous, si vous lisez attentivement le code, vous verrez qu'il convertit réellement le chemin virtuel en chemin physique ( Server.MapPath(this.VirtualPath)), donc le consommer directement sans changement est un peu naïf. Vous devez produire une alternative qui accepte PhysicalPathétant donné que c'est ce qui est finalement requis et que vous stockez. Ce serait beaucoup plus sûr car vous avez supposé que le chemin physique et le chemin relatif seraient les mêmes (à l'exclusion de la racine). Les fichiers de données sont souvent stockés est App_Data. Ce n'est pas accessible en tant que chemin relatif.
Paul Fleming

GetVirtualPath est génial .... très utile. Merci!
Zvi Redler

6

Merci à Ian Henry !

Si vous avez besoin d'obtenir un fichier de MS SQL Server, voici la solution.

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

AppModel est EntityFrameworkmodèle et MyFiles présente la table dans votre base de données. FileData est varbinary(MAX)dans la table MyFiles .


2

son simple donne juste votre chemin physique dans directoryPath avec le nom de fichier

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

Qu'en est-il du côté client, appelant cette méthode? Disons si vous voulez afficher la boîte de dialogue Enregistrer sous?
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)) return Content ("filename not present");

        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 doit fermer le fichier (ou l'ouvrir dans une utilisation). Ensuite, vous pouvez supprimer le fichier après la conversion en octets - le téléchargement se fera sur ce tampon d'octets.

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

Donc, dans votre méthode de téléchargement ...

        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
S'il vous plaît, ne chargez jamais des fichiers entiers en mémoire dans une production comme celle-ci
makhdumi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.