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:

[code:c#]

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);
    }

[/code]

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.

[code:c#]

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);
    }
}

[/code]

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:

[code:c#]

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 };
}

[/code]

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

[code:c#]

<%@ 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>

[/code]

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  

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

10 responses

  1. Avatar for Dylan Beattie
    Dylan Beattie May 28th, 2008

    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/126...

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

  2. Avatar for Swordfish
    Swordfish July 12th, 2008

    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.

  3. Avatar for OmegaSupreme
    OmegaSupreme July 26th, 2008

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

  4. Avatar for Gino Heyman
    Gino Heyman September 24th, 2008

    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.

  5. Avatar for maartenba
    maartenba September 24th, 2008

    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)

  6. Avatar for Gino Heyman
    Gino Heyman September 25th, 2008

    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 [b]any[/b] image (or other) resource... I still favor the solution you are referring to, but had to fall back to a handler.

  7. Avatar for Vasile
    Vasile August 31st, 2010

    Hi,

    BuildUrlFromExpression method is not available any more.

    Is there an equivalent to use in MVC 2?

    Thanks.

  8. Avatar for Jeremy Holovacs
    Jeremy Holovacs February 2nd, 2011

    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.

  9. Avatar for Ellery Familia
    Ellery Familia April 8th, 2011

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

  10. Avatar for Maarten Balliauw
    Maarten Balliauw July 15th, 2013

    You can use this class:

    public static class ImageResultHelper

    {

    public static string Image<t>(this HtmlHelper helper, string actionName, string controllerName, int width, int height)

    where T : Controller

    {

    return ImageResultHelper.Image<t>(helper, actionName, controllerName, new object[] { }, width, height, "");

    }

    public static string Image<t>(this HtmlHelper helper, string actionName, string controllerName, object routeValues, int width, int height)

    where T : Controller

    {

    return ImageResultHelper.Image<t>(helper, actionName, controllerName, routeValues, width, height, "");

    }

    public static string Image<t>(this HtmlHelper helper, string actionName, string controllerName, object routeValues, int width, int height, string alt)

    where T : Controller

    {

    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);

    string url = urlHelper.Action(actionName, controllerName, routeValues);

    return string.Format("<img src="\"{0}\"" width="\"{1}\"" height="\"{2}\"" alt="\"{3}\""/>", url, width, height, alt);

    }

    }