335 votes

Renvoi d'un fichier en affichage/téléchargement en ASP.NET MVC

Je rencontre un problème pour renvoyer à l'utilisateur des fichiers stockés dans une base de données en ASP.NET MVC. Ce que je veux, c'est une vue listant deux liens, l'un pour afficher le fichier et laisser le mimetype envoyé au navigateur déterminer comment il doit être traité, et l'autre pour forcer un téléchargement.

Si je choisis de visualiser un fichier appelé SomeRandomFile.bak et que le navigateur n'a pas de programme associé pour ouvrir des fichiers de ce type, alors je n'ai aucun problème à ce qu'il adopte par défaut le comportement de téléchargement. Toutefois, si je choisis d'afficher un fichier appelé SomeRandomFile.pdf o SomeRandomFile.jpg Je veux que le fichier s'ouvre simplement. Mais je veux également conserver un lien de téléchargement sur le côté afin de pouvoir forcer une invite de téléchargement quel que soit le type de fichier. Cela a-t-il un sens ?

J'ai essayé FileStreamResult et cela fonctionne pour la plupart des fichiers, son constructeur n'accepte pas de nom de fichier par défaut, donc les fichiers inconnus se voient attribuer un nom de fichier basé sur l'URL (qui ne sait pas quelle extension donner en fonction du type de contenu). Si je force le nom de fichier en le spécifiant, je perds la possibilité pour le navigateur d'ouvrir directement le fichier et j'obtiens une invite de téléchargement. Quelqu'un d'autre a-t-il rencontré ce problème ?

Voici les exemples de ce que j'ai essayé jusqu'à présent.

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

Des suggestions ?


UPDATE : Cette question semble toucher une corde sensible chez beaucoup de gens, alors j'ai pensé poster une mise à jour. L'avertissement sur la réponse acceptée ci-dessous qui a été ajouté par Oskar concernant les caractères internationaux est tout à fait valable, et je l'ai rencontré à plusieurs reprises en utilisant l'option ContentDisposition la classe. J'ai depuis mis à jour mon implémentation pour corriger cela. Bien que le code ci-dessous provienne de mon incarnation la plus récente de ce problème dans une application ASP.NET Core (Full Framework), il devrait également fonctionner avec des changements minimes dans une application MVC plus ancienne, puisque j'utilise la classe System.Net.Http.Headers.ContentDispositionHeaderValue classe.

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}

460voto

Darin Dimitrov Points 528142
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

NOTA: L'exemple de code ci-dessus ne prend pas correctement en compte les caractères internationaux dans le nom de fichier. Voir RFC6266 pour la normalisation pertinente. Je pense que les versions récentes de l'application ASP.Net MVC File() et la méthode ContentDispositionHeaderValue tient compte de ces éléments. - Oskar 2016-02-25

1 votes

Parfait, exactement ce que je cherchais et cela fonctionne exactement comme je le voulais. Je me souviens avoir utilisé ContentDisposition dans le passé, mais je ne connaissais pas la propriété Inline, et j'étais curieux de savoir s'il existait une option plus intégrée pour MVC que de faire quelque chose comme définir les en-têtes directement. Mais un simple appel à AppendHeader me semble assez élégant. Merci pour votre aide !

1 votes

Cela ne fonctionne pas dans toutes les circonstances. Certains navigateurs s'attendent à ce que le nom du fichier soit cité ( attachment ; "downloadfile.pdf" ).

8 votes

Si je me souviens bien, il peut ne pas être cité tant que le nom du fichier ne comporte pas d'espace (j'ai fait passer le mien par HttpUtility.UrlEncode() pour y parvenir).

145voto

user1814739 Points 328

J'ai eu des difficultés avec la réponse acceptée en raison de l'absence d'indication de type sur la variable "document" : var document = ... J'affiche donc ce qui a fonctionné pour moi comme alternative au cas où quelqu'un d'autre aurait des problèmes.

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}

18voto

Ashot Muradian Points 92

A ver (txt par exemple) :

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

A télécharger (txt par exemple) :

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

note : pour télécharger le fichier nous devons passer fileDownloadName argument

16voto

Leo Points 269

La réponse de Darin Dimitrov est correct. Juste un ajout :

Response.AppendHeader("Content-Disposition", cd.ToString()); peut faire échouer le rendu du fichier par le navigateur si votre réponse contient déjà un en-tête "Content-Disposition". Dans ce cas, vous pouvez utiliser :

Response.Headers.Add("Content-Disposition", cd.ToString());

5voto

Serj Sagan Points 2731

Je pense que cette réponse est plus propre, (basée sur https://stackoverflow.com/a/3007668/550975 )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }

9 votes

Non recommandé, Content-Disposition est la méthode préférée pour des raisons de clarté et de compatibilité. stackoverflow.com/questions/10615797/

0 votes

Merci pour cela. Bien que j'aie changé le type MIME en application/octet-stream et cela a encore provoqué le téléchargement du fichier plutôt que son affichage, et il semble être compatible.

2 votes

Mentir sur le type de contenu semble être une très mauvaise idée. Certains navigateurs dépendent du type de contenu correct pour suggérer des applications à l'utilisateur dans la boîte de dialogue "enregistrer ou ouvrir".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X