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"):
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.
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 oneAnonymous
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 itAnonymous
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. JeremiahAnonymous
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 removedAnonymous
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