Cutting Edge

Image Generation Service for ASP.NET 1.1

Dino Esposito

Code download available at:CuttingEdge0404.exe(134 KB)

Contents

What's IGS Anyway?
Comparing Image and DynamicImage
Comparing Pages and HTTP Handlers
Designing a ASP.NET 1.x Image Service
The CachedImageService.axd Handler
The DynamicImage Control In Action
Adding Support for Image Types
Conclusion

Sometimes you need more than just static images on your Web site—you may want to create images dynamically. Thanks to GDI+, ASP.NET 1.x makes it easy to generate images programmatically. The ASP.NET infrastructure can output images read from a source file, and can also retouch them in memory before outputting them. While this gives programmers enough power to build effective one-off solutions, it requires them to reinvent the wheel every time a new system is needed.

Aware of this limitation, the ASP.NET team designed more powerful image support into ASP.NET 2.0 and called it the Image Generation Service (IGS). With IGS, working with images requires much less code than was required in ASP.NET 1.x and lets you reference dynamically generated images in your pages more easily. You can read image data stored in a local or remote file, in database fields, or in memory. Whatever the source of the image, a new server control in IGS lets you link the image to the page.

In this column, I'll build a version of IGS targeted at ASP.NET 1.x and keep it as close as possible to the ASP.NET 2.0 implementation. This way you get tools to improve the manipulation of images in your current applications and can get ready for the new ASP.NET 2.0 features. Note that ASP.NET 2.0 is still in development and some of these APIs might change before release.

What's IGS Anyway?

No matter what kind of abstraction layer you build, to display images over the Web you still need an <img> tag pointing to a public URL. IGS is composed of two main elements—a server control and an HTTP handler. The server control is an enhanced version of the Image control currently available in ASP.NET 1.x. It's a server-side wrapper around the HTML <img> element. The HTTP handler is a helper component that provides the data behind the URL of the <img> tag that is sent out to the browser. The HTTP handler represents a common piece of server code used to generate and manipulate the bytes of the image to display.

Before I write my own version of the IGS for ASP.NET 1.x, let's briefly review the ASP.NET 2.0 DynamicImage control. In ASP.NET 2.0, the server control is named DynamicImage and, at least at first glance, looks like a close relative of the version 1.x <asp:Image> control. But the DynamicImage control has more features. Run a page that contains the following code:

<asp:DynamicImage runat="server" id="MyFileImage" ImageFile="images/tools.jpg" />

The result of running the code is that the following markup is generated for displaying an image in a browser:

<img id="MyFileImage" src="CachedImageService.axd?data={auto-generated guid}" alt="" style="border-width:0px;" />

Notice that the Src attribute is not set to the specified image file but points to an auto-generated URL. When instantiated, the control loads the image from the specified resource and stores it in the ASP.NET cache. The key used to store and retrieve the image is a GUID managed by ASP.NET.

The CachedImageService.axd URL corresponds to the public name of a built-in HTTP handler. The handler retrieves the image from the ASP.NET cache and serializes its bytes to the browser using the proper content type. The data parameter in the handler's query string indicates the key used to select the image from the ASP.NET cache. The DynamicImage control also features a read-only ImageUrl property that returns the exact URL of the image being generated.

In ASP.NET 2.0, the CachedImageService.axd service provides an additional capability. It can adjust the size and format of the image based on the detected capabilities of the browser. If the browser cannot render that type of image, the service will convert the image to a type supported on that browser, thanks to the ASP.NET 2.0 adaptive rendering engine.

The DynamicImage class inherits from the DynamicImageBase class. Figure 1 lists the properties of the DynamicImage control. As mentioned, ImageUrl is a read-only property and as such can't be used to set the URL of an image to be displayed. This may be a source of confusion because ImageUrl is just the name of the main property of the Image control in ASP.NET 1.x. (Note that dynamic images don't support width and height expressed as percentages; they can only be expressed in pixels.)

Figure 1 ASP.NET 2.0 DynamicImage Control Properties

Property Description
AutoSize True by default. Indicates whether to modify the size of the image for browsers with limited graphics capabilities.
Height Inherited from DynamicImageBase. Gets and sets the height of the image in pixels.
Image Object of type System.Drawing.Image. Gets and sets the source of the dynamic image.
ImageBytes Gets and sets a reference to a byte array that contains the dynamic image.
ImageFile Gets and sets the name of the file that contains the image.
ImageGeneratorUrl Gets and sets the URL of the HTTP handler image-generation service to use.
ImageType Inherited from DynamicImageBase. Gets and sets the preferred image type for the dynamic image.
ImageUrl Inherited from DynamicImageBase. Gets the URL used to generate the dynamic image. This property is read-only.
Parameters Gets a collection of parameters to pass to the image-generation service. This property is important when an HTTP handler is used to generate the image.
PercentScreenCover Gets and sets the percentage of the browser screen that the image will occupy. This property only applies to mobile devices and has no effect on desktop browsers.
ScaleBasedOnWidth Gets and sets a value indicating how to scale dynamic images to make them fit into the specified width and height with respect to the image aspect ratio. If true, the image is scaled based on its width, otherwise by its height.
Width Inherited from DynamicImageBase. Gets and sets the width of the image in pixels.

The DynamicImage control can acquire the image bytes from a variety of sources and render them. Four of the properties listed in Figure 1 are used to define usable image sources. They are: Image, ImageBytes, ImageFile, and ImageGeneratorUrl. Image gets and sets the image as an instance of a GDI+ image class. The property is of type System.Drawing.Image. This property is ideal for images dynamically created in memory using GDI+ capabilities. ImageBytes represents the image as an array of bytes and is suitable for images stored in databases. ImageFile gets and sets the URL to the image, whereas ImageGeneratorUrl returns the URL of a new image-specific HTTP handler having the .asix extension.

Figure 2 The Control's Architecture

Figure 2** The Control's Architecture **

Whatever the format of the image, it is internally normalized to an instance of the System.Drawing.Image object, assigned a random generated key, and cached in the ASP.NET Cache object. The diagram in Figure 2 shows an approximation of the internal architecture of the control.

Comparing Image and DynamicImage

Overall, in ASP.NET 2.0 you have two image server controls—Image and DynamicImage. Image is ideal for referencing a static image as a server-side file or URL. If you can reference an image using a file path, then use the Image control, which would produce the following familiar markup:

<img src="url" />

Referencing static images via a URL remains the fastest way to have an image downloaded to a client. No extra code is required and the browser and the Web server would handle it automatically without involving the ASP.NET runtime. IIS, for example, is optimized to serve static images such as JPG and GIF files on parallel threads without involving any ISAPI code.

In DynamicImage, the image is cached in memory rather than being read from disk or downloaded from a remote Web site on every access. However, consider that the vast majority of browsers are configured to cache images locally, which is better for performance than server-side caching anyway. The real added value of ImageFile becomes clear when you write applications designed to work with both desktop and mobile browsers. In this case the IGS can automatically adapt the image to the device, at least in the most common scenarios.

Comparing Pages and HTTP Handlers

Many developers still reference dynamic images using a URL that points to an .aspx page. Here's an example:

<img src="photo.aspx?id=9" />

If it weren't for the .aspx extension, you might think this code came from an old ASP application. In ASP.NET, however, this is a bad approach. The reason is simple: pages typically incur the most overhead of any HTTP handler you can use to serve a request. In ASP.NET, each HTTP request is served by a special component known simply as the HTTP handler. An HTTP handler is a class that implements the IHttpHandler interface—in particular, it implements the ProcessRequest method.

The Page class is an HTTP handler with a really complex and sophisticated ProcessRequest method. The method loops through the collection of child controls and instantiates them all. Next, it manages the viewstate and performs postback events. None of this is really needed to manage images. Therefore, the most efficient way to handle dynamic images in ASP.NET is to write a custom HTTP handler and optimize it to merely retrieve, serve, and possibly cache images.

In ASP.NET 1.x, you have to write such a handler yourself, and sometimes you'll find you're doing it repeatedly, one project after the next. In ASP.NET 2.0, on the other hand, you not only have the CachedImageService.axd handler available to you for immediate use, but you can afford to know practically nothing about how it works under the covers because the DynamicImage control shields you from all of the nitty-gritty details.

Designing a ASP.NET 1.x Image Service

This month's source code contains two projects—a DynamicImage custom server control and a sample application. The control assembly also includes an ImageService HTTP handler. The control mimics the functionality of the ASP.NET 2.0 DynamicImage class, while the handler presents some of the functionality of the CachedImageService.axd handler. The code in Figure 3 shows the structure of the DynamicImage control.

Figure 3 DynamicImage Control

namespace MsdnMag { public class DynamicImage : System.Web.UI.WebControls.Image { private const string IgsBaseUrl = "cachedimageservice.axd?data={0}"; public DynamicImage() { m_imageBytes = null; m_image = null; this.PreRender += new EventHandler(DynamicImage_PreRender); } // PRIVATE members private System.Drawing.Image m_image; private byte[] m_imageBytes; // PROPERTY: StorageKey protected string StorageKey { get {return Convert.ToString(ViewState["StorageKey"]);} set {ViewState["StorageKey"] = value;} } // PROPERTY: ImageUrl (override) public override string ImageUrl { get {return GetImageUrl();} set {throw new NotSupportedException();} } // PROPERTY: ImageFile public string ImageFile { get {return Convert.ToString(ViewState["ImageFile"]);} set {ViewState["ImageFile"] = value;} } [Browsable(false)] public byte[] ImageBytes { get {return m_imageBytes;} set {m_imageBytes = value;} } [Browsable(false)] public System.Drawing.Image Image { get {return m_image;} set {m_image = value;} } // More code here... } }

The DynamicImage control inherits Image and adds three new properties—ImageFile, Image, and ImageBytes. Deriving from the Image control, it also inherits the ImageUrl property that the base class exposes to let developers define the source of the resulting <img> tag. Just like its ASP.NET 2.0 counterpart, the ASP.NET 1.x DynamicImage control overrides the ImageUrl property to make it read-only. This is done by simply throwing an exception in the set accessor of the property. Not very elegant, but effective nonetheless:

public override string ImageUrl { get {return GetImageUrl();} set {throw new NotSupportedException();} }

In DynamicImage, ImageUrl plays a radically different role from the one it plays in Image. The actual URL of the image as returned by ImageUrl is determined by looking at the contents of the properties listed in Figure 4. Of course, only one of the source properties is used at a time. The control doesn't raise an error if more than one property is set, but only one is taken into account (the current ASP.NET 2.0 implementation of this control only allows one of these to be set and throws an exception otherwise). In my implementation, if set, ImageFile takes precedence over the other two. Otherwise, the control first checks ImageBytes and then Image.

Figure 4

Property Type Design-time Support Description
Image System.Drawing.Image No References an image held in memory. The image is represented with a managed object. The content of the image is loaded from a disk file, a stream, or created using GDI+ interfaces.
ImageBytes Byte[] No References the image as an array of bytes. Typically, you use this member when you want to reference an image stored in a database field.
ImageFile String Yes References the image through its file name. This is equivalent to using a static file with the <img> tag.

ImageFile fully replaces ImageUrl as implemented in the Image control, though if you want to reference an image through its physical or virtual file name, this is the property to use. ImageFile is also the only property you can set at design time. The Browsable attribute, in fact, prevents Image and ImageBytes from displaying in the Properties box, as shown here:

[Browsable(false)] public byte[] ImageBytes {...} [Browsable(false)] public System.Drawing.Image Image {...}

When you set the ImageFile property declaratively or programmatically, the markup sent to the browser looks like this:

<img src="file" ... />

The DynamicImage control outputs an <img> tag even when you set the image through the ImageBytes or Image property. HTML provides just one way to call an image: through the <img> tag. That tag can only accept input expressed as a publicly accessible URL. This means that the bytes of the image read from a database table, or the in-memory image, must be referenced using a URL. This is where the image service HTTP handler comes in:

<img src="cachedimageservice.axd?data=..." />

The constructor of the DynamicImage control registers a handler for the PreRender event. If the event fires, that means at least one source property has been set. The control figures out the highest priority source for the image content and determines the URL to use. The source code that demonstrates this feature is the GetImageUrl method in Figure 5. GetImageUrl is invoked from within the get accessor of the ImageUrl property (see Figure 3).

Figure 5 GetImageURL Method

void DynamicImage_PreRender(object sender, EventArgs e) { if (ImageUrl.Length == 0) return; // Cache bytes or image if (ImageBytes != null) StoreImageBytes(); else if (Image != null) StoreImage(); return; } string GetImageUrl() { string url = ""; // Check ImageFile if (ImageFile.Length >0) { url = ImageFile; } else // Check ImageBytes and Image { if (ImageBytes != null || Image != null) { if (StorageKey.Length == 0) { Guid g = Guid.NewGuid(); StorageKey = g.ToString(); } return GetCachedImageUrl(); } } return url; } string GetCachedImageUrl() { return String.Format(IgsBaseUrl, StorageKey); }

The PreRender event fires just before any ASP.NET 1.x control enters rendering mode. If the image is not referenced through a plain URL—the content is set through either ImageBytes or Image—the control ensures that any image data is available to the built-in HTTP handler. Each image referenced as an object or an array of bytes is given a unique key. This key is merely a GUID:

Guid g = Guid.NewGuid(); string storageKey = g.ToString();

The GUID is stored in the viewstate and preserved across page requests. This ensures that the same key always identifies the image displayed by a given control:

protected string StorageKey { get {return Convert.ToString(ViewState["StorageKey"]);} set {ViewState["StorageKey"] = value;} }

The GUID is also used as the key to cache the image object or bytes in the ASP.NET cache.

Except when the ImageFile property is set, the output of the DynamicImage control is retrieved from the cache and served to the browser by an HTTP handler. Figure 6 shows the code that stores the image content to the ASP.NET cache.

Figure 6 Store Image in Cache

private void StoreImage() { StoreData(m_image); } private void StoreImageBytes() { StoreData(m_imageBytes); } private void StoreData(object data) { if (Page.Cache[StorageKey] == null) { Page.Cache.Add(StorageKey, data, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(60), CacheItemPriority.High, null); } }

The cache entry is created only if it doesn't exist. The Add method, in fact, fails if the specified entry is found to already exist. The current storage key is used to uniquely identify the cache entry. The image is given a sliding expiration policy along with a high priority. These settings match the settings of the DynamicImage control in ASP.NET 2.0.

Sliding expiration indicates that the cached item will expire after a certain period of inactivity. If the cached item is not read or written for the specified duration, it will be removed. If the item is accessed before its natural expiration time, then the sliding period is automatically renewed.In this example, I've defined a one-minute interval—it is a longer interval in ASP.NET 2.0, but configurable through the web.config file. You indicate a sliding expiration through a TimeSpan object—a class that expresses a time interval. The FromSeconds static method creates an interval in seconds.

Each item stored in the ASP.NET cache is given a priority that is used when the system runs low on memory. Items with a lower priority are removed first to keep the system healthy.

The CachedImageService.axd Handler

As I mentioned, an HTTP handler is a piece of code used to service requests of a certain type. Typically, an HTTP handler processes requests that have a certain extension or that match a certain URL. Figure 7 shows the code of the HTTP handler used to retrieve image data from the cache.

Figure 7 Retrieve Image from Cache

using System; using System.Web; using System.Drawing.Imaging; namespace MsdnMag { public class CachedImageService : IHttpHandler { // Override the IsReusable property public bool IsReusable { get { return true; } } // Override the ProcessRequest method public void ProcessRequest(HttpContext context) { string storageKey = ""; // Retrieve the DATA query string parameter if (context.Request["data"] == null) { WriteError(); return; } else storageKey = context.Request["data"].ToString(); // Grab data from the cache object o = HttpContext.Current.Cache[storageKey]; if (o == null) { WriteError(); return; } if ((o as byte[]) != null) WriteImageBytes((byte[]) o); else if ((o as System.Drawing.Image) != null) WriteImage((System.Drawing.Image) o); } private void WriteImageBytes(byte[] img) { HttpContext.Current.Response.ContentType = "image/jpeg"; HttpContext.Current.Response.OutputStream.Write(img, 0, img.Length); } private void WriteImage(System.Drawing.Image img) { HttpContext.Current.Response.ContentType = "image/jpeg"; img.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg); } private void WriteError() { HttpContext.Current.Response.Write("No image specified"); } } }

The IHttpHandler interface has only two methods of its own, one of which (IsReusable) just returns false in most common implementations. The method value indicates whether another request can reuse the instance of the handler.

The core of the handler is in the ProcessRequest method. This method contains the logic of the handler and governs the processing of the request. The image service handler expects to find a data parameter in the query string. If the parameter is missing, the method outputs an error message:

if (context.Request["data"] == null) { WriteError(); return; } void WriteError() { HttpContext.Current.Response.Write("No image specified"); }

The data parameter represents the key of the item in the ASP.NET cache that contains the image to display. The handler grabs the image from the cache and outputs it to the browser. The image is rendered according to the storage format. If the image is stored as an array of bytes, the array is flushed to the OutputStream property of the Response object. Otherwise, if the image is a GDI+ object, the Save method of the Bitmap class targets the same stream.

The code in Figure 7 defaults to JPEG as the image format. This is arbitrary and using another image type is certainly possible. Later, I'll return to this point to design a more general solution that includes the image type as a variable argument.

The HTTP handler is a class and gets compiled in the same assembly as the DynamicImage control. How would you invoke it? ASP.NET 2.0 uses the CachedImageService.axd URL to link to it. Can the same be done in ASP.NET 1.x? Yes, of course. The mapping between an HTTP handler and a URL name (or extension) can be set declaratively in the application's web.config file. If that is going to be a system setting, then you can move the following script to the machine.config file. (This happens to be exactly what ASP.NET 2.0 does.)

<configuration> <system.web> <httpHandlers> <add verb="GET" path="cachedimageservice.axd" type="MsdnMag.CachedImageService,DynamicImage" /> </httpHandlers> </system.web> </configuration>

The <httpHandlers> section instructs the ASP.NET runtime to use the MsdnMag.CachedImageService handler class to service any incoming GET request targeted to the cachedimageservice.axd URL. The .axd URL doesn't correspond to a real server file or resource. It is just a fake name used to bind a given functionality (coded into the HTTP handler class) to a URL.

The DynamicImage Control In Action

Now that the ASP.NET 1.x image generation service is up and running, let's see how to use it in a sample application. Create your own ASP.NET project, tweak the web.config file as described previously, and reference the DynamicImage control in your toolbox. The sample page has three instances of the DynamicImage control, each set with a different source property—ImageFile, Image, and ImageBytes:

<cc1:DynamicImage id="diUrl" runat="server" ImageFile="images/msdnmag.gif" /> <cc1:DynamicImage id="diGdi" runat="server" Image='<%# GetRandomNumber() %>' /> <cc1:DynamicImage id="diDb" runat="server" ImageBytes='<%# GetEmployeePhoto(3) %>'/>

Both the Image and ImageBytes properties are bindable and can be set declaratively with the return value of any function that generates the proper data type. Figure 8 shows the source code of the two helper functions that provide the contents of the images to display. GetRandomNumber creates and returns an in-memory image that represents a randomly generated number. GetEmployeePhoto retrieves the picture of an employee from the Northwind database and attaches those bytes to the ImageBytes property of a DynamicImage control. Figure 9 shows the page in action. The grid at the bottom of the page shows the current content of the Cache object. The output is generated by a custom user control that iterates through the cache and populates a grid (see the source code in this month's download).

Figure 8 GetRandomNumber and GetEmployeePhoto

public System.Drawing.Image GetRandomNumber() { // Store the number Random gen = new Random(); int number = gen.Next(); // Generate the image Bitmap bmp = new Bitmap(200, 100); using (Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.LightCyan); using (Font f = new Font("Impact", 20)) { g.DrawString(number.ToString(), f, Brushes.Blue, 10, 10); } } return bmp; } public byte[] GetEmployeePhoto (int empID) { using (SqlConnection conn = new SqlConnection( "SERVER=localhost;DATABASE=northwind;Integrated Security=SSPI")) { using (SqlCommand cmd = new SqlCommand( "SELECT photo FROM employees WHERE employeeid=@id", conn)) { cmd.Parameters.Add("@id", empID); conn.Open(); byte[] img = (byte[]) cmd.ExecuteScalar(); conn.Close(); MemoryStream ms = new MemoryStream (img, 78, img.Length - 78); return ms.ToArray(); } } }

Figure 9 The Page in Action

Figure 9** The Page in Action **

The image in the top-left corner comes from a server-side image file. The URL of the corresponding <img> tag served to the browser matches the URL assigned to the ImageFile property of the server control. The other two images are generated dynamically. Their URLs look like this:

cachedimageservice.axd?data=2a1612b8-0d4a-4757-ab91-7a29f25c1aea

If you make a separate request for this URL you'll get the image, as long as the cached item has not been removed. The cache lifetime depends on the sliding expiration set in the DynamicImage control (see Figure 6). As mentioned, this parameter is configurable in ASP.NET 2.0 and set to five minutes by default. If the image is removed from the cache, a direct call would return an error. A call made through the control, on the other hand, would trap the error, generate a new GUID, then reload and cache the image.

Adding Support for Image Types

In the example developed thus far, the image type was assumed to be a JPEG. There's no reason you can't change it to any other type that the Microsoft® .NET Framework supports (GIF, PNG, BMP, and the like). The image type can sometimes be inferred from the image bytes and the in-memory representation of the image. More precisely, if you hold the image bytes you can apply some patterns to the array and recognize the format—all image formats begin with a well-known signature and header; however, if the image is created as a GDI+ object, then no information about the image type is stored. A GDI+ image is rendered as a format-independent object—the Bitmap class. In spite of the name, though, the .NET Framework Bitmap class simply represents an image as a matrix of RGB pixels (plus the alpha channel, for a total of 32 bits per pixel) with no relationship to a common image file format. Only when the Bitmap object is serialized does the image format become an essential parameter:

img.Save(stream, imageType)

The internal serializer looks at the desired type and converts the generic RGB format to a layout typical of JPG or GIF files.

In ASP.NET 2.0, the image type is expressed as an attribute to the DynamicImage control and stored with the cached image object. This parameter is then retrieved and used when it is time to output the image stream. To add this capability to the ASP.NET 1.x image generation service, you start by adding a new public property, called ImageType, to the DynamicImage control:

public ImageType ImageType { get {return (ImageType) ViewState["ImageType"];} set {return ViewState["ImageType"] = value;} }

The type of the property is an enumeration named ImageType. The ImageType enumeration indicates the preferred image type for dynamically generated images. It is worth noting that in ASP.NET 2.0, if the requesting browser does not support the image type you specify, the DynamicImage control automatically provides a different type of image based on the display capabilities of the particular browser in question:

public enum ImageType { Jpeg, Gif, Bmp, Png }

The ImageType property indicates the desired type of the output image. You can also build into the DynamicImage control the ability to convert the image from the original format to the desired one. If the ImageType property is not specified, you can make it default to a fixed type (JPEG) or infer it from the file name extension if the ImageFile property is set.

In any case, once the desired image type has been determined, you pack the image to cache into a new data structure, like so:

class ImageInfo { object imageObject; ImageType imageType; }

The imageObject field contains the bytes of the image if the ImageBytes property has been set, or it contains the GDI+ object if the Image property is used instead. The imageType property sets the desired output type.

It is important to note that setting the ImageType property doesn't automatically ensure that the image sent to the browser is of the right type. This is easy to code for GDI+ objects, but not if the image is read out of a database field or a file:

void WriteImage(System.Drawing.Image img) { string mimeType = ImageTypeToMimeType(); HttpContext.Current.Response.ContentType = mimeType; img.Save(HttpContext.Current.Response.OutputStream, m_imageType); }

If the image is stored as a format-independent object, then you simply need to express its type as a MIME type (for example, image/jpeg). The conversion from the RGB format to the desired one is necessary and cannot be avoided. Things are a bit different if the image is an array of bytes or a file. If it's a file, the image type is well-defined and implicit in the bytes but may not match the value of the ImageType property. The simplest way to code a control that always outputs the image as specified by ImageType, irrespective of the original format, is to convert the source format to a GDI+ object. This code shows how to load an image from a file:

FileStream fs = new FileStream(url, ...); Bitmap img = Image.FromStream(fs);

Similarly, you can load a GDI+ image from an array of bytes.

MemoryStream ms = new MemoryStream(m_imageBytes); Bitmap img = Image.FromStream(ms);

At this point, regardless of the original format, you cache the image as a generic Bitmap object and can output it in the desired format with no problem—the approach taken in ASP.NET 2.0.

Conclusion

The DynamicImage control in ASP.NET 2.0, and in the implementation provided here for ASP.NET 1.x, achieves two main goals. First, it gives you an easy way to display images that are dynamically generated and not simply read from a static file. As I demonstrated, you can also bind the properties of the new control to data-bound expressions for maximum flexibility. Just remember to place a call to Page.DataBind somewhere in the code to trigger the evaluation of any data-bound expressions throughout the page.

Second, the DynamicImage control provides a smart way to cache generated images. When static files are used, they are downloaded the first time (unless cache-specific settings prevent that) and loaded from the local browser cache when needed. One of the issues with dynamic images accessed through .aspx pages or .ashx handlers in ASP.NET 1.x, is that the image is regenerated each time it is accessed. The DynamicImage control reduces this overhead significantly by storing the generated image in the ASP.NET cache and retrieving it from there. A configurable parameter lets you decide how long the images can be stored. Each image is given a unique GUID and when the image is no longer available from the cache, it is regenerated.

Send your questions and comments for Dino to  cutting@microsoft.com.

Dino Esposito is an instructor and consultant based in Rome, Italy. Author of Programming ASP.NET (Microsoft Press, 2003), he spends most of his time teaching classes on ADO.NET and ASP.NET and speaking at conferences. Get in touch with Dino at cutting@microsoft.com or join the blog at https://weblogs.asp.net/despos.