web-dev-qa-db-fra.com

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

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

J'aimerais acheminer cette logique via un contrôleur, chaque fois qu'une URL telle que celle-ci est demandée:

www.mywebsite.com/resource/image/topbanner

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

J'ai vu des exemples de ce type dans lesquels vous devez créer une vue - je ne veux pas utiliser une vue. Je veux tout faire avec seulement le contrôleur.

Est-ce possible?

436
Jonathan

Utilisez la méthode 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");
}

A noter que cela semble assez efficace. J'ai fait un test où j'ai demandé l'image via le contrôleur (http://localhost/MyController/Image/MyImage) et via l'URL directe (http://localhost/Images/MyImage.jpg) et les résultats sont les suivants:

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

Remarque: il s’agit du temps moyen d’une demande. La moyenne a été calculée en effectuant des milliers de demandes sur la machine locale. Par conséquent, les totaux ne doivent pas inclure les problèmes de latence du réseau ou de bande passante.

516
Brian

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 est évident que j'ai quelques éléments spécifiques à l'application ici concernant la construction du chemin, mais le retour de FileStreamResult est agréable et simple.

J'ai effectué des tests de performances en ce qui concerne cette action contre votre appel quotidien à l'image (en contournant le contrôleur) et la différence entre les moyennes n'était que d'environ 3 millisecondes (la moyenne avg du contrôleur était de 68 ms, celle des non-contrôleurs était de 65 ms).

J'avais déjà essayé certaines des méthodes mentionnées dans les réponses ci-dessus et les performances étaient bien plus dramatiques ... plusieurs des solutions proposées étaient autant que 6x non contrôleurs (autres contrôleurs moy. 340 ms, non contrôleurs 65 ms).

124
Sailing Judo

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

Trois classes implémentent la classe FileResult :

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 de fichier où le fichier existe sur le disque, utilisez FilePathResult - c'est le moyen le plus simple et vous évite d'avoir à utiliser Streams.
  • Pour les tableaux d'octets [] (semblables à Response.BinaryWrite), utilisez FileContentResult.
  • Pour les tableaux d'octets [] où vous voulez télécharger le fichier (content-disposition: attachment), utilisez FileStreamResult de la même manière que ci-dessous, mais avec un MemoryStream et en utilisant GetBuffer().
  • Pour Streams, utilisez FileStreamResult. Cela s'appelle un FileStreamResult mais il faut un Stream donc je devrais deviner cela fonctionne avec un MemoryStream.

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

    [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;
    }
98
Chris S

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");
}
72
staromeste

Vous pouvez créer votre propre extension et le faire de cette façon.

public static class ImageResultHelper
{
    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height)
            where T : Controller
    {
        return ImageResultHelper.Image<T>(helper, action, width, height, "");
    }

    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height, string alt)
            where T : Controller
    {
        var expression = action.Body as MethodCallExpression;
        string actionMethodName = string.Empty;
        if (expression != null)
        {
            actionMethodName = expression.Method.Name;
        }
        string url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection).Action(actionMethodName, typeof(T).Name.Remove(typeof(T).Name.IndexOf("Controller"))).ToString();         
        //string url = LinkBuilder.BuildUrlFromExpression<T>(helper.ViewContext.RequestContext, helper.RouteCollection, action);
        return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
    }
}

public class ImageResult : ActionResult
{
    public ImageResult() { }

    public Image Image { get; set; }
    public ImageFormat ImageFormat { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        // verify properties 
        if (Image == null)
        {
            throw new ArgumentNullException("Image");
        }
        if (ImageFormat == null)
        {
            throw new ArgumentNullException("ImageFormat");
        }

        // output 
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = GetMimeType(ImageFormat);
        Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
    }

    private static string GetMimeType(ImageFormat imageFormat)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
        return codecs.First(codec => codec.FormatID == imageFormat.Guid).MimeType;
    }
}
public ActionResult Index()
    {
  return new ImageResult { Image = image, ImageFormat = ImageFormat.Jpeg };
    }
    <%=Html.Image<CapchaController>(c => c.Index(), 120, 30, "Current time")%>
10

Vous pouvez écrire directement dans la réponse, mais celle-ci n'est pas testable. Il est préférable de renvoyer un ActionResult qui a différé l'exécution. Voici mon StreamResult resusable:

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);
    }
}
9
JarrettV

Pourquoi ne pas faire simple et utiliser l’opérateur tilde ~?

public FileResult TopBanner() {
  return File("~/Content/images/topbanner.png", "image/png");
}
8
JustinStolle

MISE À JOUR: Il existe de meilleures options que ma réponse d'origine. Cela fonctionne très bien en dehors de MVC, mais il est préférable de s'en tenir aux méthodes intégrées de renvoi du contenu de l'image. Voir les réponses qui ont été votées.

Vous pouvez certainement. Essayez ces étapes:

  1. Charger l'image du disque dans un tableau d'octets
  2. met en cache l'image dans le cas où vous vous attendez à plus de demandes pour l'image et que vous ne voulez pas d'entrées/sorties de disque (mon exemple ne la met pas en cache ci-dessous)
  3. Changer le type mime via le Response.ContentType
  4. Response.BinaryEcrit le tableau d'octets de l'image

Voici un exemple de code:

string pathToFile = @"C:\Documents and Settings\some_path.jpg";
byte[] imageData = File.ReadAllBytes(pathToFile);
Response.ContentType = "image/jpg";
Response.BinaryWrite(imageData);

J'espère que ça t'as aidé!

5
Ian Suttle

Cela a fonctionné pour moi. Depuis que je stocke des images sur une base de données SQL Server.

    [HttpGet("/image/{uuid}")]
    public IActionResult GetImageFile(string uuid) {
        ActionResult actionResult = new NotFoundResult();
        var fileImage = _db.ImageFiles.Find(uuid);
        if (fileImage != null) {
            actionResult = new FileContentResult(fileImage.Data,
                fileImage.ContentType);
        }
        return actionResult;
    }

Dans l'extrait ci-dessus, _db.ImageFiles.Find(uuid) recherche l'enregistrement du fichier image dans la base de données (contexte EF). Il retourne un objet FileImage qui est simplement une classe personnalisée que j'ai créée pour le modèle, puis l'utilise comme FileContentResult.

public class FileImage {
   public string Uuid { get; set; }
   public byte[] Data { get; set; }
   public string ContentType { get; set; }
}
4
hmojica

Solution 1: rendre une image dans une vue à partir d'une URL d'image

Vous pouvez créer votre propre méthode d'extension:

public static MvcHtmlString Image(this HtmlHelper helper,string imageUrl)
{
   string tag = "<img src='{0}'/>";
   tag = string.Format(tag,imageUrl);
   return MvcHtmlString.Create(tag);
}

Alors utilisez-le comme:

@Html.Image(@Model.ImagePath);

Solution 2: Rendre l'image à partir de la base de données

Créez une méthode de contrôleur qui renvoie les données d'image comme ci-dessous

public sealed class ImageController : Controller
{
  public ActionResult View(string id)
  {
    var image = _images.LoadImage(id); //Pull image from the database.
    if (image == null) 
      return HttpNotFound();
    return File(image.Data, image.Mime);
  }
}

Et utilisez-le dans une vue comme celle-ci:

@ { Html.RenderAction("View","Image",new {[email protected]})}

Pour utiliser une image rendue à partir de cette action dans un code HTML quelconque, utilisez

<img src="http://something.com/image/view?id={imageid}>
4
Ajay Kelkar

Regardez ContentResult. Ceci retourne une chaîne, mais peut être utilisé pour créer votre propre classe similaire à BinaryResult.

3
leppie

vous pouvez utiliser File pour retourner un fichier comme View, Content etc

 public ActionResult PrintDocInfo(string Attachment)
            {
                string test = Attachment;
                if (test != string.Empty || test != "" || test != null)
                {
                    string filename = Attachment.Split('\\').Last();
                    string filepath = Attachment;
                    byte[] filedata = System.IO.File.ReadAllBytes(Attachment);
                    string contentType = MimeMapping.GetMimeMapping(Attachment);

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

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

                    return File(filedata, contentType);          
                }
                else { return Content("<h3> Patient Clinical Document Not Uploaded</h3>"); }

            }
3
Avinash Urs

Je vois deux options:

1) Implémentez votre propre IViewEngine et définissez la propriété ViewEngine du contrôleur que vous utilisez sur votre ImageViewEngine dans la méthode "image" de votre choix.

2) Utilisez une vue :-). Il suffit de changer le type de contenu, etc.

1
Matt Mitchell

Vous pouvez utiliser HttpContext.Response et y écrire directement le contenu (WriteFile () peut fonctionner pour vous), puis renvoyer ContentResult à partir de votre action au lieu d'ActionResult.

Disclaimer: Je n'ai pas essayé cela, c'est basé sur les API disponibles. :-)

1
Franci Penov
if (!System.IO.File.Exists(filePath))
    return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
    return new FilePathResult(filePath, contentType);

SomeHelper.EmptyImageResult() doit renvoyer FileResult avec une image existante (1x1 transparent, par exemple).

C'est le moyen le plus simple si vous avez des fichiers stockés sur le lecteur local. Si les fichiers sont byte[] ou stream - utilisez alors FileContentResult ou FileStreamResult comme suggéré par Dylan.

1
Victor Gelmutdinov

J'ai aussi rencontré une exigence similaire,

Donc, dans mon cas, je fais une demande au contrôleur avec le chemin du dossier de l'image, qui en retour renvoie un objet ImageResult.

L'extrait de code suivant illustre le travail:

var src = string.Format("/GenericGrid.mvc/DocumentPreviewImageLink?fullpath={0}&routingId={1}&siteCode={2}", fullFilePath, metaInfo.RoutingId, da.SiteCode);

                if (enlarged)
                    result = "<a class='thumbnail' href='#thumb'>" +
                        "<img src='" + src + "' height='66px' border='0' />" +
                        "<span><img src='" + src + "' /></span>" +
                        "</a>";
                else
                    result = "<span><img src='" + src + "' height='150px' border='0' /></span>";

Et dans le contrôleur à partir du chemin de l'image, je produis l'image et la renvoie à l'appelant.

try
{
  var file = new FileInfo(fullpath);
  if (!file.Exists)
     return string.Empty;


  var image = new WebImage(fullpath);
  return new ImageResult(new MemoryStream(image.GetBytes()), "image/jpg");


}
catch(Exception ex)
{
  return "File Error : "+ex.ToString();
}
0