views:

10589

answers:

11

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?

A: 

I see two options:

1) Implement your own IViewEngine and set the ViewEngine property of the Controller you are using to your ImageViewEngine in your desired "image" method.

2) Use a view :-). Just change the content type etc.

Graphain
A: 

Look at ContentResult. This returns a string, but can be used to make your own BinaryResult-like class.

leppie
A: 

You could use the HttpContext.Response and directly write the content to it (WriteFile() might work for you) and then return ContentResult from your action instead of ActionResult.

Disclaimer: I have not tried this, it's based on looking at the available APIs. :-)

Franci Penov
Yeah I just noticed ContentResult only supports strings, but it's easy enough to make your own ActionResult based class.
leppie
A: 

You certainly can. Try out these steps:

  1. Load the image from disk in to a byte array
  2. cache the image in the case you expect more requests for the image and don't want the disk I/O (my sample doesn't cache it below)
  3. Change the mime type via the Response.ContentType
  4. Response.BinaryWrite out the image byte array

Here's some sample code:

string pathToFile = @"C:\Documents and Settings\some_path.jpg";
byte[] imageData = File.ReadAllBytes(pathToFile);
Response.ContentType = "image/jpg";
Response.BinaryWrite(imageData);

Hope that helps!

Ian Suttle
+5  A: 
Dylan Beattie
FileResult is an abstract class. How can you use it in this way?
Al Katawazi
This will not compile. See my answer for more info...
Brian
ANSWER BELOW IS MUCH BETTER!!!!
Jaap
+1  A: 

You can write directly to the response but then it isn't testable. It is preferred to return an ActionResult that has deferred execution. Here is my resusable StreamResult:

public class StreamResult : ViewResult
{
    public Stream Stream { get; set; }
    public string ContentType { get; set; }
    public string ETag { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
     context.HttpContext.Response.ContentType = ContentType;
     if (ETag != null) context.HttpContext.Response.AddHeader("ETag", ETag);
     const int size = 4096;
     byte[] bytes = new byte[size];
     int numBytes;
     while ((numBytes = Stream.Read(bytes, 0, size)) > 0)
      context.HttpContext.Response.OutputStream.Write(bytes, 0, numBytes);
    }
}
JarrettV
+5  A: 

I asked a similar question here http://stackoverflow.com/questions/155906/creating-a-private-photo-gallery-using-aspnet-mvc and ended up finding a great guide to do this.

I created an ImageResult class following this guide. http://blog.maartenballiauw.be/post/2008/05/ASPNET-MVC-custom-ActionResult.aspx

Vyrotek
use this guide! works great. just be aware you probably want to change the encoding parameters if your image files are too big
Simon_Weaver
+25  A: 

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

Sailing Judo
This worked very well. Very simple and efficient.
caryden
i second that? Solved my problem
mazhar kaunain baig
What about image is not modified? FileStreamResult should send 304 when image is not modified since last request.
dario-g
+57  A: 

Use the base controllers File method.

public ActionResult Image(string id)
{
    var dir = Server.MapPath("/Images");
    var path = Path.Combine(dir, id + ".jpg");
    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.

Brian
For those that are coming into this question now, this was the solution that worked best for me.
Clarence Klopfstein
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.
Jason Slocomb
Good catch @Jason. I updated my answer.
Brian
Dude you got robbed bro.
Pierreten
Crap, I gotta remember to have the best answer but not the selected answer. Awesome rep farming technique!
Will
I suppose if you want your question selected as "the answer" you've got to answer it in the same year it was asked :).
Brian
+6  A: 

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 FileContentResult 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()
{
    using (FileStream stream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + "/myimage.png"))
    {
     FileStreamResult result = new FileStreamResult(stream, "image/png");
     result.FileDownloadName = "image.png";
     return result;
    }
}
Chris S
This helped me. Thanks!
Judah Himango
The content-disposition part of this post was extremely helpful
Diego
A: 
if (!System.IO.File.Exists(filePath))
    return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
    return new FilePathResult(filePath, contentType);

SomeHelper.EmptyImageResult() should return FileResult with existing image (1x1 transparent, for example).

This is easiest way if you have files stored on local drive. If files are byte[] or stream - then use FileContentResult or FileStreamResult as Dylan suggested.

Lion_cl