Dynamic image generation in custom controls

Onion Blog

Syndication

I was teaching our Applied ASP.NET course the other week to a client that needed to do some server-side image generation from large sets of telemetry data, and I thought I'd share a technique for doing this from within a custom control that I showed them.
 
The problem you quickly run into as you try to do something like this is that you not only need to render an image tag to the client, but you somehow need the src='' attribute to turn into a separate request for the image content that is also handled by your custom control. If you're serving back existing images in a known directory on the server, this is easy enough - you can do something like:
 
protected override void Render(HtmlTextWriter writer)
{
  string src = "img/myimage.jpg";
  // dynamically set src to whatever image you want here
  writer.AddAttribute(HtmlTextWriterAttribute.Src, imgSrc);
  writer.RenderBeginTag(HtmlTextWriterTag.Img);
  writer.RenderEndTag();
  base.Render (writer);
}
 
You could even take the approach of dynamically generating images, saving them to a directory and setting the src attribute to the newly generated image. The problem is, as a control, you have no idea of where you are going to deployed, what the directory structure is, and whether you will even have enough privileges to write files to the directory. You'd also have to deal with cleaning up the image files (or not :).
 
So another approach is to render a src attribute url that you can handle within your control in a second request (essentially hijacking the request early on in the Page event lifecycle). The technique that I've used is to set the src url equal to the same request url that was issued requesting my control to render, but with an appended query string that only my control knows about. Inside of my Init event handler (before even the Page class has had a chance to do much processing with the request) my control renders the image, sets the mime type appropriately, and terminates the response.
 
Here's a sample implementation that takes this approach - first, I derive from the Image control base class, since it already renders the img tag properly:
 
namespace Pluralsight.Samples
{
  public class DynamicImage : System.Web.UI.WebControls.Image
  {
    //...
  }
}
 
Next, I override the ImageUrl property and make it non-browsable (so it doesn't show up in the properties window of VS.NET):
 
[Browsable(false)]
public override string ImageUrl
{
  get { return base.ImageUrl;  }
  set { base.ImageUrl = value; }
}
 
In the Load event handler, set the ImageUrl property to the current URL with a query string appended that you know will be unique and recognized only by your control (I used a GUID in this case):
 
protected override void OnLoad(EventArgs e)
{
  string url = Page.Request.FilePath.Substring(
                     Page.Request.FilePath.LastIndexOf("/") + 1);
  url += "?renderImg= 288e19a4-1b58-437b-9583-64beb4740704";
  base.ImageUrl = url;
 
  base.OnLoad (e);
}
 
Now comes the tricky part - when the request is made for the image, the page containing your control will be created and asked to service the request. Instead of going through the standard rendering sequence however, we intercept the request in the Init Event and peremptorily send back a response containing our dynamically generated image before the rest of the page is even aware that a request has been made:
 
protected override void OnInit(EventArgs e)
{
  MemoryStream stm;
  if ((Page.Request.QueryString["renderImg"] != null) &&
      (Page.Request.QueryString["renderImg"].ToString() ==
                         "288e19a4-1b58-437b-9583-64beb4740704"))
  {
    base.Page.Response.ContentType = "image/bmp";
    using (stm = new MemoryStream())
    using (Bitmap bmp = GetImage())
    {
      bmp.Save(stm, ImageFormat.Bmp);
      bmp.Dispose();
 
      Page.Response.BinaryWrite(stm.ToArray());
    }
       
    Page.Response.End();
  }
 
  base.OnInit (e);
}
 
Now the only thing left to do is generate a bitmap (by writing the GetImage() method used in our Init handler). This sample implementation here renders a Pie-chart with red and blue pieces, but you could of course generate anything you like with GDI+ calls at this point:
 
readonly Size cImageSize = new Size(400,400);
readonly Rectangle cImageRect = new Rectangle(0,0,400, 400);
 
private Bitmap GetImage()
{
  Bitmap bmp = new Bitmap(cImageSize.Width, cImageSize.Height);
  using (Graphics g = Graphics.FromImage(bmp))
  {
    g.FillRectangle(new SolidBrush(Color.White), cImageRect);
    g.FillPie(new SolidBrush(Color.Red), cImageRect, 0, 120);
    g.FillPie(new SolidBrush(Color.Blue), cImageRect, 121, 240);
  }
  return bmp;
}
 
Now, without doing any file manipulation, you have a control that renders images dynamically when embedded in any .aspx page!
 
Update: Several people have pointed out that this technique depends on the Page being 'normal' to work properly. It would not work if the parent page had enabled output caching for example, or if URL mangling was being used (which may drop off the query string). As a result, this should probably only be used in a fairly controlled environment where you know that it will work with all pages it is placed on. The other possibility is to just create a separate handler and install it in the deployed application to render the images (the only disadvantage being that you have to modify the configuration file to do this). One other technique would be to write a custom handler in a .ashx file, and set the image url to that file (the only disadvantage here being that the client would have to install this .ashx file as part of his/her application).

Posted Feb 11 2005, 08:23 AM by fritz-onion
Filed under:

Comments

Sergio Pereira wrote re: Dynamic image generation in custom controls
on 02-11-2005 9:53 AM
That's a very slick trick. I normally end up creating IHttpHandlers and asking the application developers to put a required <httpHandler .. /> tag in their config files (which sucks). I wish there was a way to programatically add httpHandlers at runtime, if there's one I couldn't find it yet.
Haacked wrote re: Dynamic image generation in custom controls
on 02-11-2005 5:36 PM
Good stuff Fritz! One minor minor issue. In the OnInit method, you create a Bitmap, call Save on it, and then call Dispose.

Shouldn't that be in a using block (or a try finally) since in case bmp.Save() throws an exception and Dispose isn't given a chance to be called?
John Waterson wrote re: Dynamic image generation in custom controls
on 02-12-2005 4:07 PM
That's super-clever, but I can't help thinking it's perhaps a bit too clever. It makes a control-level assumption that its okay to hijack page-level requests.

That works great when the page is just rendering out some dumb HTML, but in my experience that's pretty rare. People do all kinds of clever things with pages. They fiddle with HTTP headers. They process input. They cache output. They redirect off to other pages. They track and report on usage.

Serving images from the page URL could potentially mess up any of these things. Conversely, any of these things might mess up your image generation. And it all happens on the sly - there is nothing to warn of these consequences when you drop the control onto your form.

Personally I think that the relationship between pages of HTML and their dependent resources is complicated and fragile enough to make it worth being explicit. You want a control that renders dynamic images? Okay fine, here you go, but be aware that you're now tightly coupled to GetImage.aspx or whatever actually does the image generation.

It might not be as slick looking at first glance, but it's a darn sight less confusing when things go wrong.
OdeToCode Links wrote OdeToCode Links For February 13
on 02-13-2005 5:57 PM
TeunD wrote re: Dynamic image generation in custom controls
on 02-14-2005 1:01 AM
Interesting technique. OutputCaching would ruin it of course. Also, when you have many images on your page (as in menu items), you wouldn't want the image requests to carry Session (because that would force them all to be handled by the same thread).
miguel _ N O S P A M _ @clearscreen.com (Miguel Ji wrote Re: Dynamic image generation in custom controls
on 02-14-2005 1:32 AM
This was a the smarter way I though to solve a captcha image server about three months ago because it was the less intrussive. <br /><br />If you need to have a separate image server, you can generate a httphandler that serves your image and configure it through your web.config file, so you don't get dummy code on a page that is not related to the problem (image serving)
Ajay wrote re: Dynamic image generation in custom controls
on 02-14-2005 2:27 AM
The solution is clever but it hides a lot of details. I am not sure if such tight coupling between the control and resource request events is desirable. People are always doing clever stuff like URL Rewriting, custom http handlers etc which could really mess up this.
Fritz Onion wrote re: Dynamic image generation in custom controls
on 02-14-2005 4:07 AM
Good point about disposing of the bitmap - I have updated to the post to use a 'using' statement.
Fritz Onion wrote re: Dynamic image generation in custom controls
on 02-14-2005 4:09 AM
Several good points about this only working if the Page is 'normal' (no output caching, no URL mangling, etc.). I have updated the post to mention this caveat with alternative implementation ideas.
Fritz Onion wrote re: Dynamic image generation in custom controls
on 02-14-2005 4:10 AM
TeunD - good point about OutputCaching. However, what you state about threading and Session is no longer true. Carrying session across multiple requests has no impact on the thread selection (unless you run with aspcompat='true' and are making calls out to an STA-threaded COM object).
Steve wrote re: Dynamic image generation in custom controls
on 02-14-2005 5:50 AM
Its a great technique, but any ideas how you would get it working in the designer?
Christophe Fouquet wrote re: Dynamic image generation in custom controls
on 02-15-2005 4:41 AM
Cool stuff Fritz,

Actually, I had to do something like this last week. The first idea that came to mind is to use a handler. Then I remembered seeing this somewhere.
http://msdn.microsoft.com/msdnmag/issues/04/04/CuttingEdge/default.aspx

I took it and modified it to fit my needs. The .axd extension is already mapped by default so it works out well.
Tim wrote re: Dynamic image generation in custom controls
on 02-16-2005 1:05 PM
Cool trick.
What type of object do you create the DynamicImage class as so you can use it in the designer? UserControl? Class? Or do you just insert an Image and change the URL?
Thanks - Tim
haacked wrote re: Dynamic image generation in custom controls
on 02-28-2005 4:28 PM
Not to be nitpicky, but another bug. In the Render method, you probably meant

writer.AddAttribute(HtmlTextWriterAttribute.Src, src);

And not imgSrc.
you've been HAACKED wrote re: Using Embedded Resources for Client Script Blocks in ASP.NET
on 05-03-2005 7:19 AM
Ramesh wrote re: Dynamic image generation in custom controls
on 06-29-2006 1:09 AM
Its really super. I got some design issues while i use ur code. I already put some controls in my aspx page along with the custom image control.

While i run this page i am able to see the custom image control only. The all other controls are gone.

I thing i missed something. But i dont know which one.
心悦 wrote Dealing with images in content management systems, Part 1
on 05-21-2007 4:46 PM
Introduction Mostweb-basedcontentmanagementsystemsofferavarietyoftoolstohelpcontributor...
Lee wrote re: Dynamic image generation in custom controls
on 07-30-2007 2:45 AM
When I run the page it opens the image in MS Paint!

Should I implement the Render override?
Stephen Hewison wrote re: Dynamic image generation in custom controls
on 09-04-2007 1:53 AM
Genius.... Thanks mate. This was just what I needed. Worked first time.
Thomas wrote re: Dynamic image generation in custom controls
on 04-02-2008 12:42 PM
As a test, I modified the code so I could set the pie colors to my liking. This works fine for a single image, but when I add multiple images, whether dropped on the page or added dynamically, the colors all come out exactly same. When stepping through the code I can see each dynamic image being created, set, and added to the controls, but it still comes out all alike. The color for one is the same color that is set on the FIRST image. Any ideas?

Add a Comment

(required)  
(optional)
(required)  
Remember Me?