480 votes

Un contrôleur ASP.NET MVC peut-il renvoyer une image ?

Puis-je créer un contrôleur qui renvoie simplement une image ?

Je voudrais faire passer cette logique par un contrôleur, chaque fois qu'une URL telle que la suivante est demandée :

www.mywebsite.com/resource/image/topbanner

Le contrôleur va chercher topbanner.png et renvoyer cette image directement au client.

J'ai vu des exemples où il fallait créer une vue, mais je ne veux pas utiliser de vue. Je veux faire tout cela avec le contrôleur.

Est-ce possible ?

1 votes

J'ai posé une question similaire ici https://stackoverflow.com/questions/155906/creating-a-private-photo-gallery-using-aspnet-mvc et j'ai fini par trouver un excellent guide pour faire cela. J'ai créé une classe ImageResult en suivant ce guide. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html

2 votes

Si vous voulez modifier l'image, utiliser le module HttpModule ImageResizing.Net pour obtenir les meilleures performances. Dans le cas contraire, un FilePathResult n'ajoute que quelques pour cent de surcharge. La réécriture d'URL en ajoute un peu moins.

1 votes

Pourquoi ne pas utiliser le contrôleur WebApi au lieu de MVC ? ApiController class

555voto

Brian Points 13596

Utilisez la méthode du fichier des contrôleurs de base.

public ActionResult Image(string id)
{
    var dir = Server.MapPath("/Images");
    var path = Path.Combine(dir, id + ".jpg"); //validate the path for security or use other means to generate the path.
    return base.File(path, "image/jpeg");
}

À titre d'information, cela semble être assez efficace. J'ai fait un test où j'ai demandé l'image par le biais du contrôleur ( http://localhost/MyController/Image/MyImage ) et par l'URL directe ( http://localhost/Images/MyImage.jpg ) et les résultats étaient :

  • MVC : 7,6 millisecondes par photo
  • Direct : 6,7 millisecondes par photo

Note : il s'agit du temps moyen d'une requête. La moyenne a été calculée en faisant des milliers de requêtes sur la machine locale, donc les totaux ne doivent pas inclure les problèmes de latence du réseau ou de bande passante.

10 votes

Pour ceux qui se posent la question maintenant, c'est la solution qui a le mieux fonctionné pour moi.

2 votes

Excellente réponse Brian. Une chose, le paramètre contentType dans l'appel de la méthode File() devrait être "image/jpeg" dans votre exemple. Sinon, IE8 affiche une boîte de dialogue de téléchargement. Cette réponse devrait également être sélectionnée.

188 votes

Ce n'est pas un code sûr. Laisser l'utilisateur passer un nom de fichier (chemin) comme cela signifie qu'il peut potentiellement accéder aux fichiers depuis n'importe où sur le serveur. Il faudrait peut-être avertir les gens de ne pas l'utiliser tel quel.

138voto

Sailing Judo Points 4121

En utilisant la version release de MVC, voici ce que je fais :

[AcceptVerbs(HttpVerbs.Get)]
[OutputCache(CacheProfile = "CustomerImages")]
public FileResult Show(int customerId, string imageName)
{
    var path = string.Concat(ConfigData.ImagesDirectory, customerId, "\\", imageName);
    return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
}

Il y a évidemment des éléments spécifiques à l'application concernant la construction du chemin, mais le retour du FileStreamResult est simple et agréable.

J'ai effectué quelques tests de performance concernant cette action par rapport à votre appel quotidien à l'image (en contournant le contrôleur) et la différence entre les moyennes n'était que de 3 millisecondes environ (la moyenne du contrôleur était de 68 ms, celle du non-contrôleur de 65 ms).

J'ai essayé certaines des autres méthodes mentionnées dans les réponses ici et les performances ont été beaucoup plus dramatiques ... plusieurs des réponses des solutions ont été jusqu'à 6x le non-contrôleur (autres contrôleurs avg 340ms, non-contrôleur 65ms).

12 votes

Et si l'image n'est pas modifiée ? FileStreamResult doit envoyer 304 lorsque l'image n'a pas été modifiée depuis la dernière demande.

0 votes

Vous pouvez utiliser Path.Combine au lieu de la concat pour un code plus sûr et plus lisible.

104voto

Chris S Points 32376

Pour développer légèrement la réponse de Dyland :

Trois classes implémentent le Résultat du fichier classe :

System.Web.Mvc.FileResult
      System.Web.Mvc.FileContentResult
      System.Web.Mvc.FilePathResult
      System.Web.Mvc.FileStreamResult

Ils sont tous assez explicites :

  • Pour les téléchargements de chemin d'accès de fichier où le fichier existe sur le disque, utilisez FilePathResult - c'est le moyen le plus simple et qui vous évite d'avoir à utiliser Streams.
  • Pour les tableaux d'octets[] (semblable à Response.BinaryWrite), utilisez FileContentResult .
  • Pour les tableaux d'octets[] où vous souhaitez que le fichier soit téléchargé (content-disposition : attachment), utilisez FileStreamResult d'une manière similaire à celle décrite ci-dessous, mais avec un MemoryStream et en utilisant GetBuffer() .
  • Pour Streams utiliser FileStreamResult . Il s'agit d'un FileStreamResult mais il prend un Stream donc je devinez il fonctionne avec un MemoryStream .

Vous trouverez ci-dessous un exemple d'utilisation de la technique du contenu-disposition (non testé) :

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult GetFile()
    {
        // No need to dispose the stream, MVC does it for you
        string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "myimage.png");
        FileStream stream = new FileStream(path, FileMode.Open);
        FileStreamResult result = new FileStreamResult(stream, "image/png");
        result.FileDownloadName = "image.png";
        return result;
    }

2 votes

La partie contenu-disposition de cet article était extrêmement utile.

0 votes

VS me dit que cette surcharge de FileStream() est obsolète.

1 votes

Remarque : si le nom de votre fichier comporte une virgule, Chrome le rejettera en émettant l'erreur "too many headers received". Remplacez donc toutes les virgules par un "-" ou un "".

76voto

staromeste Points 471

Cela peut être utile si vous souhaitez modifier l'image avant de la renvoyer :

public ActionResult GetModifiedImage()
{
    Image image = Image.FromFile(Path.Combine(Server.MapPath("/Content/images"), "image.png"));

    using (Graphics g = Graphics.FromImage(image))
    {
        // do something with the Graphics (eg. write "Hello World!")
        string text = "Hello World!";

        // Create font and brush.
        Font drawFont = new Font("Arial", 10);
        SolidBrush drawBrush = new SolidBrush(Color.Black);

        // Create point for upper-left corner of drawing.
        PointF stringPoint = new PointF(0, 0);

        g.DrawString(text, drawFont, drawBrush, stringPoint);
    }

    MemoryStream ms = new MemoryStream();

    image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);

    return File(ms.ToArray(), "image/png");
}

1 votes

Merci. C'est parfait pour le scénario où un proxy est nécessaire pour télécharger une image nécessitant une authentification qui ne peut être effectuée du côté client.

1 votes

Vous oubliez de vous débarrasser de trois objets natifs : Font, SolidBrush et Image.

3 votes

Amélioration suggérée ici : vous créez un flux de mémoire, écrivez les données et créez ensuite un résultat File avec les données en utilisant .ToArray() Vous pourriez aussi simplement appeler ms.Seek(0, SeekOrigin.Begin) et ensuite retourner File(ms, "image/png") // retourner le flux lui-même

11voto

JarrettV Points 9099

Vous pouvez écrire directement dans la réponse, mais dans ce cas, elle n'est pas testable. Il est préférable de retourner un ActionResult dont l'exécution est différée. Voici mon StreamResult réutilisable :

public class StreamResult : ViewResult
{
    public Stream Stream { get; set; }
    public string ContentType { get; set; }
    public string ETag { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = ContentType;
        if (ETag != null) context.HttpContext.Response.AddHeader("ETag", ETag);
        const int size = 4096;
        byte[] bytes = new byte[size];
        int numBytes;
        while ((numBytes = Stream.Read(bytes, 0, size)) > 0)
            context.HttpContext.Response.OutputStream.Write(bytes, 0, numBytes);
    }
}

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