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