Generating GDI+ Images for ASP.NET MVC Views

This post is about applying the same technique I presented for ASP.NET Web Forms, but this time for MVC.

We need to have an ActionResult that does the actual work of converting whatever we draw in the Graphics context into an image with an inline Data URI. Here’s a possible solution, where you only specify the dimensions of the image to be generated:

public sealed class InlineImageResult : ActionResult
{
    private readonly Image bmp;
 
    public InlineImageResult(Int32 width, Int32 height)
    {
        //the PixelFormat argument is required if we want to have transparency
        this.bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
 
        this.Graphics = Graphics.FromImage(this.bmp);
        //for higher quality
        this.Graphics.CompositingQuality = CompositingQuality.HighQuality;
        this.Graphics.SmoothingMode = SmoothingMode.HighQuality;
        this.Graphics.InterpolationMode = InterpolationMode.High;
        //make the background transparent
        this.Graphics.Clear(Color.Transparent);
    }
 
    public Graphics Graphics { get; private set; }
 
    public override void ExecuteResult(ControllerContext context)
    {
        using (this.bmp)
        using (this.Graphics)
        using (var stream = new MemoryStream())
        {
            //PNG because of transparency
            var format = ImageFormat.Png;
 
            this.bmp.Save(stream, format);
 
            var img = String.Format("<img src=\"data:image/{0};base64,{1}\"/>", format.ToString().ToLower(), Convert.ToBase64String(stream.ToArray()));
 
            context.HttpContext.Response.Write(img);
        }
    }
}

Now, we need to create an instance of this result and draw in it, in a controller’s action method:

[ChildActionOnly]
public ActionResult Image()
{
    var result = new InlineImageResult(200, 50);
    result.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);
 
    return result;
}

Notice the ChildActionOnly attribute: it tells MVC that this action method is only meant to be used in child action (RenderAction) call, not as a stand-alone action, like this:

<p>
    @{ Html.RenderAction("Image"); }
</p>

As you can see, this provides a very convenient way to generate images on the fly. You just need to learn about the drawing methods in GDI+.




                             

6 Comments

  • @Html.RenderAction("Image")

    instead of

    @{ Html.RenderAction("Image"); }

    A bit cleaner

  • Sebastiaan:
    Thanks! Indeed! Cleaner and... not working! :-(
    See for yourself. The problem is that @ expects a method that returns a string or an IHtmlString.

  • Microsoft advises against using GDI+ in ASP.NET contexts.

    Just take a look at the top level MSDN page for the System.Drawing namespace:

    https://msdn.microsoft.com/en-us/library/System.Drawing(v=vs.110).aspx

    The warning is:

    "Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components."

    This is particularly a problem in high traffic situation (as GDI+ resource can get exhausted on a busy web site).

  • Oded:
    Did you read http://www.asprangers.com/post/2012/03/23/Why-you-should-not-use-SystemDrawing-from-ASPNET-applications.aspx, where it is explained WHY that rule exists? At the end, you will find this: "Best option is to explicitly call Dispose/Close on EACH Drawing object as soon as done with it, so that you does not depend on single Finalizer thread to clean/dispose up and thus avoid problem for leaking/orphaned critical sections and avoiding deadlock"
    Thanks, anyway! ;-)

  • Yes, disposing of each drawing object should always happen.

    Though, in that article he also says this is the short term solution... moving drawing to use WIC or WPF (which uses WIC under the hood) is the supported (and apparently faster) option.

  • Oded:
    So, your point is, it is faster to use WIC than GDI+?
    Sure, use whatever you like! Looking forward to reading your version! ;-)

Add a Comment

As it will appear on the website

Not displayed

Your website