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