4

I have images in the database and I want to return the image for viewing from an action. Here's my action.

public FileContentResult Index(ItemImageRequest request)
{
    var result = queueService.GetItemImage(request);

    if (result.TotalResults == 0)
        return File(new byte[0], "image/jpeg");

    var image = result.FirstResult;

    return File(image.Image, "image/tif");
}

I have also tried this code

public FileStreamResult Index(ItemImageRequest request)
{
    //retrieval omitted

    var image = result.FirstResult;

    System.IO.Stream stream = new System.IO.MemoryStream(image.Image);

    return new FileStreamResult(stream, "image/tif");
}

When I go to my action in the browser it prompts me for download. I don't want it to download, I want it to show in the browser. How do I accomplish this?

Josh
  • 16,286
  • 25
  • 113
  • 158

3 Answers3

8

If you use return Controller.File(filename, ...), you'll return a FilePathResult, which probably isn't what you want - I'm assuming that 'image' in your sample is an image filename (a case where 'var' isn't helping anyone...)

If you use one of the other File overloads, or use FileContentResult or FileStreamResult directly, you'll get the effect you want.

You don't need to make a custom ActionResult class (though of course, it might be useful for other reasons.)

HOWEVER, having just written all this, I've realised that your problem is that TIFF is not a file format which browsers can always (ever?) display internally, which is probably why they're prompting for a download.

You will need to re-render it to a PNG or something on the server to display in a browser.

Will Dean
  • 39,055
  • 11
  • 90
  • 118
  • bingo. I used a modification of John's ImageResult and your suggestion of converting the tiff to a png and it works now. – Josh Mar 02 '11 at 20:15
5

The File ActionResult adds a line to the HTTP header like this:

Content-disposition: attachment; filename=foo

This will cause the browser to attempt to download the file. That's why there is an overload to specify the filename.

You can create your own ActionResult to do the downloading, and omit the Content-disposition.

If you want to copy-and-paste, I have the code for one on codeplex: ImageResult.cs

John Gietzen
  • 48,783
  • 32
  • 145
  • 190
  • I tried that with code found at http://byatool.com/mvc/asp-net-mvc-upload-image-to-database-and-show-image-dynamically-using-a-view/ and I still get a download prompt. – Josh Mar 02 '11 at 19:40
  • I tried your code as well and I still get a download prompt in chrome, ie, and firefox, even after clearing the cache. – Josh Mar 02 '11 at 19:50
1

As requested, here is my solution.

Here is the ImageResult class, copied and modified from John

public class ImageResult : ActionResult
{
    public ImageResult()
    {
    }

    public byte[] ImageData
    {
        get;
        set;
    }

    public MemoryStream ImageStream
    {
        get;
        set;
    }

    public string MimeType
    {
        get;
        set;
    }

    public HttpCacheability Cacheability
    {
        get;
        set;
    }

    public string ETag
    {
        get;
        set;
    }

    public DateTime? Expires
    {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (this.ImageData == null && ImageStream == null)
        {
            throw new ArgumentNullException("ImageData or ImageStream");
        }

        if (string.IsNullOrEmpty(this.MimeType))
        {
            throw new ArgumentNullException("MimeType");
        }

        context.HttpContext.Response.ContentType = this.MimeType;

        if (!string.IsNullOrEmpty(this.ETag))
        {
            context.HttpContext.Response.Cache.SetETag(this.ETag);
        }

        if (this.Expires.HasValue)
        {
            context.HttpContext.Response.Cache.SetCacheability(this.Cacheability);
            context.HttpContext.Response.Cache.SetExpires(this.Expires.Value);
        }

        if (ImageStream != null)
        {
            ImageData = ImageStream.ToArray();
        } 

        context.HttpContext.Response.OutputStream.Write(this.ImageData, 0, this.ImageData.Length);

    }
}

Here is my action, modified for clarity

public ActionResult Index(ItemImageRequest request)
{
    var result = queueService.GetItemImage(request);

    if (result.TotalResults == 0)
        return new EmptyResult();

    ItemImage image = result.FirstResult;

    //image.Image is type byte[]
    MemoryStream tiffStream = new MemoryStream(image.Image);

    MemoryStream pngStream = new MemoryStream();

    System.Drawing.Bitmap.FromStream(tiffStream).Save(pngStream, System.Drawing.Imaging.ImageFormat.Png);

    return new ImageResult()
    {
        ImageStream = pngStream,
        MimeType = "image/png",
        Cacheability = HttpCacheability.NoCache
    };
}

Thanks to John and Will for helping me out with this.

Josh
  • 16,286
  • 25
  • 113
  • 158
  • Thanks. I have 2 comments: (1) Do you think that since the need for the tif to png conversion is common to browsers that this functionality could be kept inside the ImageResult class instead of the Controller? I could see this being useful for replicating the functionality? (2) I think that you should use private setters on the properties and inject the ImageStream or ImageData along with the MimeType into the ImageResult. If you did these two things then your controller action would become very thing and this would be much more reusable. – smartcaveman Mar 03 '11 at 16:19
  • I had thought about doing that, but I only have 1 type of image served up this way. I wanted to keep the conversion logic out of the ImageResult to keep the result as reusable as possible. and on number 2, I just copied the code and modified it to accept the MemoryStream the same way it was currently accepting other parameters. – Josh Mar 03 '11 at 17:17
  • How do you call it from the View? – Literate Corvette Mar 01 '23 at 17:41