Can an ASP.NET MVC controller return an Image?

255,771

Solution 1

Use the base controllers File method.

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

As a note, this seems to be fairly efficient. I did a test where I requested the image through the controller (http://localhost/MyController/Image/MyImage) and through the direct URL (http://localhost/Images/MyImage.jpg) and the results were:

  • MVC: 7.6 milliseconds per photo
  • Direct: 6.7 milliseconds per photo

Note: this is the average time of a request. The average was calculated by making thousands of requests on the local machine, so the totals should not include network latency or bandwidth issues.

Solution 2

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).

Solution 3

To expland on Dyland's response slightly:

Three classes implement the FileResult class:

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

They're all fairly self explanatory:

  • For file path downloads where the file exists on disk, use FilePathResult - this is the easiest way and avoids you having to use Streams.
  • For byte[] arrays (akin to Response.BinaryWrite), use FileContentResult.
  • For byte[] arrays where you want the file to download (content-disposition: attachment), use FileStreamResult in a similar way to below, but with a MemoryStream and using GetBuffer().
  • For Streams use FileStreamResult. It's called a FileStreamResult but it takes a Stream so I'd guess it works with a MemoryStream.

Below is an example of using the content-disposition technique (not tested):

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

Solution 4

This might be helpful if you'd like to modify the image before returning it:

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

Solution 5

You can create your own extension and do this way.

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")%>
Share:
255,771
Jonathan
Author by

Jonathan

Hello, I'm Jonathan! 👋 Experienced in Front End Development (HTML, CSS, ES6, Angular, React), Back End Development (C#, Java, NodeJS) and Software Engineering (OOP, RDBMS, TDD and beginning to branch into FP). Additionally trained and experienced in Interaction Design (User Research, Sketching, Wireframing, Prototyping, Accessibility). Focused on user needs, with an emphasis on relationships, empathy, evidence and results. I enjoy working in diverse, multi-disciplinary teams, learning and sharing knowledge. Made significant and lasting contributions to several high-impact projects in Australia, for: Bupa (2010), Westpac (2013), Service NSW (2015) and the Digital Transformation Agency (2017). Worked remotely for 5+ clients (including production support and across time-zones), with high self-motivation, productivity and communication (over email, IM and pull-requests). • Continuously programming since 2000 •

Updated on July 08, 2022

Comments

  • Jonathan
    Jonathan almost 2 years

    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?

  • leppie
    leppie over 15 years
    Yeah I just noticed ContentResult only supports strings, but it's easy enough to make your own ActionResult based class.
  • Clarence Klopfstein
    Clarence Klopfstein over 14 years
    For those that are coming into this question now, this was the solution that worked best for me.
  • Jason Slocomb
    Jason Slocomb almost 14 years
    Great answer Brian. One thing, the contentType parameter in the File() method call should be "image/jpeg" in your example. IE8 pops a download dialog otherwise. Also this answer should be selected.
  • dariol
    dariol over 13 years
    What about image is not modified? FileStreamResult should send 304 when image is not modified since last request.
  • Diego
    Diego over 13 years
    The content-disposition part of this post was extremely helpful
  • Ian Mercer
    Ian Mercer over 13 years
    This isn't safe code. Letting the user pass a file name (path) like this means they could potentially access files from anywhere on the server. Might want to warn people not to use it as-is.
  • CRice
    CRice about 13 years
    and how would this look in the controller's action?
  • mare
    mare almost 13 years
    This answer doesn't make any sense. If you are working with files that are saved on a server inside some folder under your web app root, it is better and faster to just use Views and Html or Url MVC helpers to construct the path to the images for the <img> tag or make your own helpers to render the <img> tag entirely. The only time using FileResult makes sense is when you have files, images or any other kind of byte[] content saved in the datastore.
  • Nikwin
    Nikwin almost 13 years
    Unless you are constructing the files on the fly as they are needed and caching them once they are created (that's what we do).
  • Elan Hasson
    Elan Hasson over 12 years
    This could be trouble due to extra spaces or CRLFs in the View.
  • Elan Hasson
    Elan Hasson over 12 years
    I was wrong in my last post...msdn.microsoft.com/en-us/library/… You can use WebImage class and WebImage.Write in a view :)
  • Matt
    Matt over 12 years
    I used this to do the same with a PDF. +1 Thanks
  • Russ Cam
    Russ Cam over 12 years
    @mare- you might also do this if you are serving files from a restricted location e.g. you may have images in App_Data that should be sign by some users of your application but not others. Using a controller action to serve them allows you to restrict access.
  • MrBoJangles
    MrBoJangles over 12 years
    VS is telling me that this overload of FileStream() is obsolete.
  • Hong
    Hong about 12 years
    Thank you. This is perfect for the scenario where a proxy is needed to download an image requiring authentication that cannot be done on the client side.
  • MordechayS
    MordechayS about 12 years
    Something to note: if you have a comma in your file name, Chrome will reject it with a "too many headers received" error. So replace all commas with a "-" or "".
  • RonnBlack
    RonnBlack almost 12 years
    I'm not positive but a quick test of this solution showed that it ignores the "If-Modified-Since" headers and will always return the file. That may be desireable in some situations but it will circumvent client side caching and hurt performance.
  • Wout
    Wout over 11 years
    You're forgetting to dispose of a whopping 3 native objects: Font, SolidBrush and Image.
  • Quango
    Quango over 11 years
    Suggested improvement here: you create a memory stream, write the data and then create a File result with the data using .ToArray() You could also just call ms.Seek(0, SeekOrigin.Begin) and then return File(ms, "image/png") // return the stream itself
  • AaronLS
    AaronLS about 10 years
    As other have mentioned, be cautious in your path building, as I've seen actual production code that allowed user to navigate up a directory with carefully constructed POST or query string: /../../../danger/someFileTheyTHoughtWasInaccessible
  • Marcell Toth
    Marcell Toth over 5 years
    You can use Path.Combine instead of the concat for safer and more readable code.
  • Goodies
    Goodies almost 4 years
    Tested, but corrected a small bug in this code example.. Used Edit.. "result" should be "bitmap" in the Save statement. Really useful example, thx ! +1
  • DevOhrion
    DevOhrion over 2 years
    Yes directory traversal vulnerabilities are no joke.