Can an ASP.NET MVC controller return an Image?

Viewed 262420

Can I create a Controller that simply returns an image asset?

I would like to route this logic through a controller, whenever a URL such as the following is requested:

www.mywebsite.com/resource/image/topbanner

The controller will look up topbanner.png and send that image directly back to the client.

I've seen examples of this where you have to create a View - I don't want to use a View. I want to do it all with just the Controller.

Is this possible?

20 Answers

Using the release version of MVC, here is what I do:

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

I obviously have some application specific stuff in here regarding the path construction, but the returning of the FileStreamResult is nice and simple.

I did some performance testing in regards to this action against your everyday call to the image (bypassing the controller) and the difference between the averages was only about 3 milliseconds (controller avg was 68ms, non-controller was 65ms).

I had tried some of the other methods mentioned in answers here and the performance hit was much more dramatic... several of the solutions responses were as much as 6x the non-controller (other controllers avg 340ms, non-controller 65ms).

You can write directly to the response but then it isn't testable. It is preferred to return an ActionResult that has deferred execution. Here is my resusable StreamResult:

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

UPDATE: There are better options than my original answer. This works outside of MVC quite well but it's better to stick with the built-in methods of returning image content. See up-voted answers.

You certainly can. Try out these steps:

  1. Load the image from disk in to a byte array
  2. cache the image in the case you expect more requests for the image and don't want the disk I/O (my sample doesn't cache it below)
  3. Change the mime type via the Response.ContentType
  4. Response.BinaryWrite out the image byte array

Here's some sample code:

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

Hope that helps!

Below code utilizes System.Drawing.Bitmap to load the image.

using System.Drawing;
using System.Drawing.Imaging;

public IActionResult Get()
{
    string filename = "Image/test.jpg";
    var bitmap = new Bitmap(filename);

    var ms = new System.IO.MemoryStream();
    bitmap.Save(ms, ImageFormat.Jpeg);
    ms.Position = 0;
    return new FileStreamResult(ms, "image/jpeg");
}

Look at ContentResult. This returns a string, but can be used to make your own BinaryResult-like class.

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

SomeHelper.EmptyImageResult() should return FileResult with existing image (1x1 transparent, for example).

This is easiest way if you have files stored on local drive. If files are byte[] or stream - then use FileContentResult or FileStreamResult as Dylan suggested.

Read the image, convert it to byte[], then return a File() with a content type.

public ActionResult ImageResult(Image image, ImageFormat format, string contentType) {
  using (var stream = new MemoryStream())
    {
      image.Save(stream, format);
      return File(stream.ToArray(), contentType);
    }
  }
}

Here are the usings:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;

I see two options:

1) Implement your own IViewEngine and set the ViewEngine property of the Controller you are using to your ImageViewEngine in your desired "image" method.

2) Use a view :-). Just change the content type etc.

You could use the HttpContext.Response and directly write the content to it (WriteFile() might work for you) and then return ContentResult from your action instead of ActionResult.

Disclaimer: I have not tried this, it's based on looking at the available APIs. :-)

I also encountered similar requirement,

So in my case I make a request to Controller with the image folder path, which in return sends back a ImageResult object.

Following code snippet illustrate the work:

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

And in the Controller from the the image path I produce the image and return it back to the caller

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

Yes you can return Image

public ActionResult GetImage(string imageFileName)
{
    var path = Path.Combine(Server.MapPath("/Images"), imageFileName + ".jpg"); 
    return base.File(path, "image/jpeg");
}

(Please don't forget to mark this as answer)

From a byte[] under Core 3.2, you can use:

public ActionResult Img(int? id) {
    MemoryStream ms = new MemoryStream(GetBytes(id));
    return new FileStreamResult(ms, "image/png");
}
Related