Extending MVC: Returning an Image from a Controller Action


So I was thinking tonight, what if I want my MVC application to serve images that are stored in a SQL database as binary data? Or files that are stored in the database?   One of the things that I really like about MVC is that ability to add custom functionality in a fairly simple way.  A few days ago, I wrote a post showing how to implement a custom HtmlHelper method for rendering a group of checkboxes.  In this post, we are going to create a custom ActionResult class that can serve an image as the return value from a controller action method.

In the typical controller action method, a View is returned.  This allows the controller to render the View as the response for the web request.  You are probably familiar with this if you have been using MVC.  Here is a common example of this:

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Index()
   4:     {
   5:         ViewData["Title"] = "Home Page";
   6:         ViewData["Message"] = "Welcome to ASP.NET MVC!";
   7:  
   8:         return View();
   9:     }
  10: }

In the code above, View() is a method on the Controller class that returns a ViewResult.  ViewResult is a class that inherits from ActionResult.  The ViewResult class finds the associated view and renders the view to the output stream of the web response.  JsonResult is another type that can be returned in an action method.  JsonResult performs javascript serialization on some data and writes the serialized data to the web response.

So to answer our initial question, let’s create an ImageResult class that will write the bytes of an image to the web response.  The code below is fairly straightforward – we inherit from ActionResult, define some properties that we need, set the properties in the constructor, and then create and return the response.

   1: public class ImageResult : ActionResult
   2: {
   3:     public ImageResult(Stream imageStream, string contentType)
   4:     {
   5:         if (imageStream == null)
   6:             throw new ArgumentNullException("imageStream");
   7:         if (contentType == null)
   8:             throw new ArgumentNullException("contentType");
   9:  
  10:         this.ImageStream = imageStream;
  11:         this.ContentType = contentType;
  12:     }
  13:  
  14:     public Stream ImageStream { get; private set; }
  15:     public string ContentType { get; private set; }
  16:  
  17:     public override void ExecuteResult(ControllerContext context)
  18:     {
  19:         if (context == null)
  20:             throw new ArgumentNullException("context");
  21:  
  22:         HttpResponseBase response = context.HttpContext.Response;
  23:  
  24:         response.ContentType = this.ContentType;
  25:         
  26:         byte[] buffer = new byte[4096];
  27:         while (true)
  28:         {
  29:             int read = this.ImageStream.Read(buffer, 0, buffer.Length);
  30:             if (read == 0)
  31:                 break;
  32:  
  33:             response.OutputStream.Write(buffer, 0, read);
  34:         }
  35:  
  36:         response.End();
  37:     }
  38: }

The next thing that I did was create a set of extension methods for the Controller class.  All that these methods do is create and return a new instance of the ImageResult class based on the arguments.  This allows you to pass the image as either a stream or a byte array.

   1: public static class ControllerExtensions
   2: {
   3:     public static ImageResult Image(this Controller controller, Stream imageStream, string contentType)
   4:     {
   5:         return new ImageResult(imageStream, contentType);
   6:     }
   7:  
   8:     public static ImageResult Image(this Controller controller, byte[] imageBytes, string contentType)
   9:     {
  10:         return new ImageResult(new MemoryStream(imageBytes), contentType);
  11:     }
  12: }

Now that we have that done, let’s create a controller action method to get the images.  We just need to specify the binary image data and the content type for the image.

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Images(string id)
   4:     {
   5:         // Here is where we would take the id that was passed as
   6:         // the argument and get the image from the database or 
   7:         // filesystem.  In this example though, I am just going to 
   8:         // return the same image from my local hard drive 
   9:         // regardless of the id parameter.
  10:  
  11:         byte[] image = File.ReadAllBytes(@"C:\netLogo.jpg");
  12:         string contentType = "image/jpeg";
  13:  
  14:         // Here we are calling the extension method and returning    
  15:         // the ImageResult.
  16:         return this.Image(image, contentType);
  17:     }
  18: }

And we are done, and it was pretty easy.  In just a few dozen lines of code, we have provided a way to return images from a controller action without the need to create a view.  The result looks are shown below (and notice how we can user a meaningful URL, rather than something like "GetFile.aspx?filename=netLogo.jpg"):

image

We could do the same thing to return files from a database server by creating a FileResult class that is similar to ImageResult.  In fact, the only difference needed is to change the content type to "application/octet-stream" and then the browser will prompt the user for where to save the file.

image

Comments (17)

  1. Paolo Izmoto says:

    Hi Clark,

    Nice one!

    Thank you very much indeed.

  2. Paolo Izmoto says:

    Hi,

    The solution works from within the dev environment.

    When i deploy my mvc web app to an iis7 server, clicking on a link to view an image stored in a sql server database works ok (if browsing from the iis7 machine) but returns a 500 server error (if browsing from a remote machine).  I can’t seem to figure out waht the issue is 🙁

  3. taelor says:

    Next Step: Render this image using an ajax request.

    Anyone tried to do this? I’ve implemented this same feature on the controller side in Rails, but now I need to render this image stream using javascript. Anyone have any suggestions?

  4. rickjac says:

    This post starts out to ender an image from a database in MVC but ends with pulling am image from a file tih is somethinh you can do with no code just an image control I’ve been looking for an artical that shows how to display an image from a database table that has a Guid UserId has any one seen one

  5. rickjac,

     The code above just uses the bytes from a file to provide a quick sample.  Assuming that you have the image data stored in the database in a varbinary column, you can still use the same code above and just change to action method to get the byte[] or stream from the database instead of a file.

  6. rickjac says:

    I’ve tried to reproduce this code so I could try to tweek it around and get it to render an image out of a data base the code falls apart for me when I get to

    byte[] image = File.ReadAllBytes(@"C:netLogo.jpg");

    the error I get is System.Web,Mvc.Controller.File(string, string, string ) is a method , which is not valid in the given context

    this is a frustrating issue I’ve searched google,  Windows live search

    and I’ve checked every fourm I could find lots of Questions on the matter no answers many search results cliam to pull from data base

    in MVC but when I get to the site they do pretty much the same as this and end up pulling from a file I’m a novice and I’m not that good at mvc that I could do it on my own so if there is an artlcial or blog that looks at this problem I would be happy to find it

  7. rickjac,

     What are you using for your database access?  Plain old ADO.NET?  Linq-to-SQL?  Entity Framework?  If you can give me an idea of what you are using to get the byte array from the database, I can likely provide you with a code snippet to fix your problem.

  8. Martin Kwong says:

    for rickyjac, i found the reason to this error, because the File.ReadAllBytes() is suppose to be system.IO.File.ReadAllBytes(),

    However system.web.mvc.controller also have a File method.

    just add "System.IO." infront of your File.ReadAllBytes()

  9. Bobby Turkings says:

    Thankyou works like a charm you beauty!

  10. Bobby Turkings says:

    I wanted to take the code above and display an image for each product in a list. to do that i used this code:

    <img src="<%= Url.Action("ShowImage", "Image", new { Id = imageId }) %>" />

    On my View. U know the rest !

  11. PG says:

    I think I achieved the same thing without any extra code using FileContentResult

    —–            

    return new FileContentResult(attachment.FileContent, attachment.ContentType);

    —–

    where attachment.FileContent is a byte[]

    and attachment.ContentType is a string e.g. image/jpeg

  12. PG,

     Yes, that is the way to do it as of the MVC 1.0 release.  This post is a little outdated and was done before that ActionResult was added to the code.

     Thanks for the comment, I should probably update the post to make sure that people are aware of the preferred way.

    Jeremiah

  13. Lasse Edsvik says:

    I've discovered a strange behaviour with with this example when using MVC3 and EF5….. When I run it on webserver locally, or on a real server it gets 10 times slower after a while, usually around 30 minutes. I have set the webserver application pool to recycle once a day. The memory used for the process is about the same at start and when it starts to slow. The files retrieved from database are between 10-50kb, so its not exactly large files… Goes from 25ms to 250-450ms

    var thumbnail = _fileRep.GetThumbnail(fileID, width);

    return this.Image(thumbnail.FileContent, contentType);

    How can I figure out whats wrong?

  14. anil says:

    Get and display particular image along with data from database using ASP.NET MVC

    4 hours, 59 minutes ago|LINK

    Table:

    public int ActorID { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

    public string Price { get; set; }

    [DataType(DataType.ImageUrl)]

    public string Image { get; set; }

    My Controller code:

    public ActionResult Details(int id)

    {

    Actor ac = db.Actors.Single(x => x.ActorID == id);

    return View(ac);

    View:

    @model MvcApplication1.Models.Actor

    @{

    ViewBag.Title = "Details";

    }

    <div style="font-family:Arial">

    <h2>Details</h2>

    <fieldset>

    <legend>Actor</legend>

    <div class="display-label">

    @Html.DisplayNameFor(model => model.Name)

    </div>

    <div class="display-field">

    @Html.DisplayFor(model => model.Name)

    </div>

    <div class="display-label">

    @Html.DisplayNameFor(model => model.Description)

    </div>

    <div class="display-field">

    @Html.DisplayFor(model => model.Description)

    </div>

    <div class="display-label">

    @Html.DisplayNameFor(model => model.Price)

    </div>

    <div class="display-field">

    @Html.DisplayFor(model => model.Price)

    </div>

    <div class="display-label">

    @Html.DisplayNameFor(model => model.Image)

    </div>

    <div class="display-field">

    @*@Html.DisplayFor(model => model.Image)*@

    <img src="@Url.Content(@Model.Image)"/>

    </div>

    </fieldset>

    </div>

    <p>

    @Html.ActionLink("Edit", "Edit", new { id=Model.ActorID }) |

    @Html.ActionLink("Back to List", "Index")

    </p>

    This is my code At the time of clicking Details option It will display data correctly But ,Along with IMAGE not displaying,

    please Correct my above code

  15. Marek Bar says:

    Hi folks,

    Before while loop stream position should be set to 0 because it may not read data. It happened to me so please check this before use.

  16. your code has lots of spots that aren't correct says:

    your code has lots of spots that aren't correct