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

Développeur
la source
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);
}
DARSHAN SHINDE
la source
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);
        }
hossein zakizadeh
la source
-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);
Caio Augusto
la source
-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));
CDichter
la source
2
S'il vous plaît, ne chargez jamais des fichiers entiers en mémoire dans une production comme celle-ci
makhdumi