Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

About the author

Maarten Balliauw is currently employed as a Technical Evangelist at JetBrains. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Pro NuGet Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
Maarten Balliauw - MVP - Most Valuable Professional
Maarten Balliauw - ASPInsider

Search

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright Maarten Balliauw 2013


ASP.NET MVC custom ActionResult (ImageResult)

The ASP.NET MVC framework introduces the concept of returning an ActionResult in Controllers since the "preview preview" release on CodePlex. The purpose of this concept is to return a generic ActionResult object for each Controller method, allowing different child classes returning different results.

An example ActionResult (built-in) is the RenderViewResult. Whenever you want to render a view, you can simply return an object of this class which will render a specific view in its ExecuteResult method. Another example is the HttpRedirectResult which will output an HTTP header (Location: /SomethingElse.aspx).

In my opinion, this is a great concept, because it allows you to develop ASP.NET MVC applications more transparently. In this blog post, I will build a custom ActionResult class which will render an image to the HTTP response stream.

ASP.NET MVC Custom ActionResultAs an example, let's create a page which displays the current time as an image.

One option for implementing this would be creating an ASP.NET HttpHandler which renders this image and can be used inline with a simple HTML tag: <img src="DisplayTime.ashx" />

Wouldn't it be nice to be able to do something more ASP.NET MVC-like? Let's consider the following: <%=Html.Image<HomeController>(c => c.DisplayTime(), 200, 50, "Current time")%>

1. Creating the necessary HtmlHelper extension methods

The above example code is not available in standard ASP.NET MVC source code. We'll need an extension method for this, which will map a specific controller action, width, height and alternate text to a standard HTML image tag:

public static class ImageResultHelper
{
    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height)
        where T : Controller
    {
        return ImageResultHelper.Image<T>(helper, action, width, height, "");
    }

    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height, string alt)
        where T : Controller
    {
        string url = helper.BuildUrlFromExpression<T>(action);
        return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
    }

2. The custom ActionResult class

Our new ImageResult class will inherit the abstract class ActionResult and implement its ExecuteResult method. This method basically performs communication over the HTTP response stream.

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

    public Image Image { get; set; }
    public ImageFormat ImageFormat { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        // verify properties
        if (Image == null)
        {
            throw new ArgumentNullException("Image");
        }
        if (ImageFormat == null)
        {
            throw new ArgumentNullException("ImageFormat");
        }

        // output
        context.HttpContext.Response.Clear();

        if (ImageFormat.Equals(ImageFormat.Bmp)) context.HttpContext.Response.ContentType = "image/bmp";
        if (ImageFormat.Equals(ImageFormat.Gif)) context.HttpContext.Response.ContentType = "image/gif";
        if (ImageFormat.Equals(ImageFormat.Icon)) context.HttpContext.Response.ContentType = "image/vnd.microsoft.icon";
        if (ImageFormat.Equals(ImageFormat.Jpeg)) context.HttpContext.Response.ContentType = "image/jpeg";
        if (ImageFormat.Equals(ImageFormat.Png)) context.HttpContext.Response.ContentType = "image/png";
        if (ImageFormat.Equals(ImageFormat.Tiff)) context.HttpContext.Response.ContentType = "image/tiff";
        if (ImageFormat.Equals(ImageFormat.Wmf)) context.HttpContext.Response.ContentType = "image/wmf";

        Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
    }
}

3. A "DisplayTime" action on the HomeController

We'll add a DisplayTime action on the HomeController class, which will return an instance of the newly created class ImageResult:

public ActionResult DisplayTime()
{
    Bitmap bmp = new Bitmap(200, 50);
    Graphics g = Graphics.FromImage(bmp);

    g.FillRectangle(Brushes.White, 0, 0, 200, 50);
    g.DrawString(DateTime.Now.ToShortTimeString(), new Font("Arial", 32), Brushes.Red, new PointF(0, 0));

    return new ImageResult { Image = bmp, ImageFormat = ImageFormat.Jpeg };
}

And just to be complete, here's the markup of the index view on the HomeController:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Code" %>
<%@ Import Namespace="MvcApplication1.Controllers" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <p>
        <%=Html.Image<HomeController>(c => c.DisplayTime(), 200, 50, "Current time")%>
    </p>
</asp:Content>

Want the source code? Download it here! You can use it with the current ASP.NET MVC framework source code drop.

kick it on DotNetKicks.com  


Categories: ASP.NET | C# | General | MVC | Personal | Software

Comments (9) -

Dylan Beattie |

Tuesday, May 27, 2008 7:27 PM

Dylan Beattie

This code looks great, makes sense... but, alas, it doesn't work for me! (on MVC Preview 2 April code refresh)

If anyone else is getting MissingMethodException for Response.get_OutputStream(), I've started a thread over at

http://forums.asp.net/t/1266968.aspx

to see if it's a known issue or if there's a workaround.

Swordfish Netherlands |

Saturday, July 12, 2008 12:02 AM

Swordfish

Dylan have you've read the article (not just viewing the pictures)? ActionResults are new to preview 3, so it doesn't work for preview 2..
(sorry, but I had to get it out of my system..)

A few comments:
- Why not using the width and height from the Image extention method also to define the size of the image (both are equal, but the bitmap is now hardcoded to 200x50)
- It would write the if block for the contenttype as switch block for readability.

OmegaSupreme United Kingdom |

Friday, July 25, 2008 11:05 PM

OmegaSupreme

I love MVC, so powerful. Nice work !!!

Gino Heyman Belgium |

Wednesday, September 24, 2008 9:02 AM

Gino Heyman

Just bumped up to this post via the MSDN forum and thought I add this footnote.
I've done basically the same thing on a medium scale web application, because I thought it was the right thing to do... until I noticed how long it took for my thumbnail (and other) images to render - there are about 600 hundred of them (times 3, they exist in three sizes) and rendered 16 at a time on a paged view.
I've plugged my previous IHttphandler implementation back in and the page load time went from about 2 seconds back to 'not noticeable'.
I still think that what you describe is conceptually better, but just not feasible in all circumstances. I guess the performance hit of resolving controllers and action methods, applying filters and executing the results is just to high for these kinds of requests. Let's hope that gets better in future releases.

maartenba Belgium |

Wednesday, September 24, 2008 9:20 AM

maartenba

Gino, I agree with that for larger web sites. Another way to go there is just creating thumbnails once and storing them on disk. If you have a reverse proxy enabled on your server farm, images will be cached and your server will not notice any performance issues at all for these thumbnails. Conclusion is that the approach to take is really depending on the situation.

(sidenote: the HtmlHelper extension method could also be used for calling a regular IHttpHandler somewhere, making it easy to include an automatically generated image in a view)

Gino Heyman Belgium |

Wednesday, September 24, 2008 6:33 PM

Gino Heyman

That was not my point. Sorry, I wasn't entirely clear.
I am not generating images on the fly, they are already stored on disk, but due to some circumstances I am not willing to let IIS handle the images directly; hence the need for a custom route/handler. (I also set the cache expiration header values appropriately.)
I was just pointing out (just a footnote) that the ASP.Net MVC overhead itself is fine for pages (and a few extra requests per page maybe), but not necessarily appropriate for any image (or other) resource... I still favor the solution you are referring to, but had to fall back to a handler.

Vasile Romania |

Tuesday, August 31, 2010 1:44 PM

Vasile

Hi,

BuildUrlFromExpression method is not available any more.

Is there an equivalent to use in MVC 2?

Thanks.

Jeremy Holovacs United States |

Tuesday, February 01, 2011 6:26 PM

Jeremy Holovacs

Fantastic.  Works like a champ.  I did end up putting the MIME types into a switch statement off ImageFormat.ToString(), I think it looked cleaner that way and then it was easier to trap a bad image format and throw an exception.

Ellery Familia United States |

Friday, April 08, 2011 5:36 PM

Ellery Familia

BuildUrlFromExpression method isn't available on MVC 2 or 3. Is there an alternative?

Comments are closed