Freigeben über


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

  • Anonymous
    November 13, 2008
    PingBack from http://mstechnews.info/2008/11/extending-mvc-returning-an-image-from-a-controller-action/

  • Anonymous
    December 15, 2008
    Hi Clark, Nice one! Thank you very much indeed.

  • Anonymous
    December 17, 2008
    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 :(

  • Anonymous
    March 26, 2009
    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?

  • Anonymous
    April 29, 2009
    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

  • Anonymous
    April 30, 2009
    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.

  • Anonymous
    May 03, 2009
    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

  • Anonymous
    May 04, 2009
    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.

  • Anonymous
    May 18, 2009
    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()

  • Anonymous
    October 03, 2009
    Thankyou works like a charm you beauty!

  • Anonymous
    October 04, 2009
    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 !

  • Anonymous
    October 23, 2009
    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

  • Anonymous
    November 04, 2009
    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

  • Anonymous
    March 25, 2013
    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?

  • Anonymous
    July 11, 2013
    The comment has been removed

  • Anonymous
    November 24, 2014
    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.

  • Anonymous
    December 10, 2015
    your code has lots of spots that aren't correct