When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, April 22, 2009

Dynamically Generating and Caching Images in ASP.NET with the GeneratedImage Control

By Scott Mitchell


Introduction


CodePlex is Microsoft's open source project community and provides a free hosting platform for open source projects created in .NET. Microsoft's own ASP.NET team has its own CodePlex page - aspnet.CodePlex.com - where they give code previews for upcoming releases. There you'll find the ASP.NET MVC 1.0 source code, a preview of the ASP.NET AJAX 4.0 Framework, and other future libraries, frameworks, and controls.

One of the controls on the ASP.NET team's CodePlex page that gets little press is the GeneratedImage control. The GeneratedImage control is a combination of an ASP.NET Web Control and a set of classes that facilitate programmatically creating, serving, caching, and transforming images. If you store images in the database that need to be served from a web page, if you need to create images on the fly, or if you need to resize, add watermarks, or perform some other image transform, then the GeneratedImage control can help.

This article looks at using the GeneratedImage control. Specifically, we'll see how to generate dynamic images on the fly based on a variety of inputs. We'll also look at how to cache images. Read on to learn more!

- continued -

The Impetus for Programmatically Serving Images


In most websites, images are static files and are displayed on a web page through the <img> HTML element or the ASP.NET Image Web control (which renders an <img> element). However, there are times when images need to be served dynamically. Consider a dating website where members can upload pictures of themselves. These pictures need to be stored somewhere, either on the web server's file system or directly in the database. If the images are stored on the file system then they can be displayed by an <img> HTML element or the ASP.NET Image Web control, but if the images are stored within the database then special steps must be taken to extract the image from the database and send it down to the client. This typically entails creating an ASP.NET page whose sole purpose is to serve images from the database. (See Storing Binary Files Directly in the Database Using ASP.NET 2.0 for how to use an ASP.NET page to serve content stored in a database.)

Other cases when being able to serve images dynamically include scenarios where the image needs to be generated on the fly or when an image - either static or dynamic - needs to be modified in some way before being served. Consider an image gallery website where users can upload pictures taken from a digital camera. Chances are these uploaded images are going to be quite large in both their dimensions and file size. When displaying a users images through a web page you might want to resize the image to a maximum width or height so that the image fits within the page's design. While you can constrain an image's dimensions via the ASP.NET Image control's Width and Height properties, doing so only affects how the browser sizes the image. The full image is still sent down to the client. However, resizing the image on the server and then sending this resized image to the browser can greatly reduce your site's total bandwidth and provide a snappier user experience.

In order to programmatically serve an image you need to create an HTTP Handler on the server whose sole responsibility is to perform whatever image manipulations are needed - constructing the image, resizing it, caching it on disk, and so forth - and then returns it to the client. An HTTP Handler is an object in an ASP.NET application that serves content. All ASP.NET pages are themselves HTTP Handlers, so it is possible to create an ASP.NET page whose responsibility is to process and serve an image. However, it's also possible (and a bit more efficient) to create a slimmer, more focused HTTP Handler. (For more information on how HTTP Handlers work and how to create your own HTTP Handlers, refer to How ASP.NET Web Pages are Processed on the Web Server and Serving Dynamic Content with HTTP Handlers.)

Once the HTTP Handler (or ASP.NET page) has been created, the images can be served from an <img> HTML element or ASP.NET Image Web control by having the image reference the HTTP Handler (or ASP.NET page). For instance, imagine that you have a database table that stores information about employees and contains a varbinary column in the Employees table that stores the binary contents of a picture of each employee. To view this picture from a web page on the intranet you would first create a page (or HTTP Handler) that serves the picture, namely a page that connects to the database, pulls down the binary contents of the picture for a particular employee, and then returns that binary content to the requesting client. Image that this page was named ShowEmployeePicture.aspx and that it was passed an EmployeeID value through its querystring to determine which employee's picture to display. In other words, visiting ShowEmployeePicture.aspx?EmployeeID=1 would display the picture for the employee whose EmployeeID is 1.

To display a particular employee's picture in a web page you might use an ASP.NET Image Web control and set its ImageUrl property to ShowEmployeePicture.aspx?EmployeeID=ID. This would send down HTML like <img src="ShowEmployeePicture.aspx?EmployeeID=ID" /> to the browser, which would cause the browser to make a request to ShowEmployeePicture.aspx?EmployeeID=ID. ShowEmployeePicture.aspx?EmployeeID=ID would then go get the binary contents of the picture for employee ID and return that to the browser, which would then display the picture.

Introducing the GeneratedImage Control


The GeneratedImage control is a free, open source project from Microsoft's ASP.NET team that facilities creating an HTTP Handler for programmatically generating, caching, and transforming images at runtime on the server. Moreover, this project includes a Web control for displaying dynamic images via an HTTP Handler. The GeneratedImage control was released in August 2008 and works with ASP.NET version 3.5 SP1 and beyond. One thing to keep in mind is that these projects that the ASP.NET team releases on CodePlex are not guaranteed to appear in future releases. The team uses their CodePlex project as sort of a laboratory where they can try out new projects, get feedback, and see how it's used in the wild. Scott Hanselman describes these CodePlex projects thusly:
"There's a treasure trove of interesting stuff over at http://www.codeplex.com/aspnet. It's a bunch of potential future stuff that the ASP.NET team is working on. That's where you can get builds of ASP.NET MVC, Dynamic Data Futures, new AJAX stuff, [and the GeneratedImage control]. ... It's a potential new feature, so it could go nowhere, or we could make T-shirts and sing its praises. Could go either way. No promises.
So keep in mind that this control is not officially part of the ASP.NET stack, nor is it officially supported by Microsoft or guaranteed to be in any future releases. Moreover, there are some loose ends with the GeneratedImage control. For instance, the GeneratedImage control library includes a mechanism to implement caching dynamically-generated images on the web server. Right now there's only one implementation, to cache the image on the web server's file system. This implementation has some hard-coded decisions that should really be configurable, such as the duration for which the image is cached on the file system. Furthermore, the infrastructure's present to allow for other caching stores, such as caching the dynamically-generated images in memory, yet there is no memory cache option. Perhaps these loose ends will be tied up and the GeneratedImage control will be released as part of ASP.NET 4.0 (or beyond).

The GeneratedImage control has been added to the Visual Studio Toolbox.

Downloading the GeneratedImage Control and Adding It To Your Website


The first step to getting started with the GeneratedImage control is to download the control, which you can do from its Releases page in the CodePlex project: http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=16449. At minimum you need to download the Microsoft.Web.GeneratedImage.dll assembly; you can also download a project road map and a variety of samples of the GeneratedImage control in action.

To use the GeneratedImage control in an ASP.NET application you need to add this assembly to your project's Bin folder. If you use Visual Studio's Web Site Project model then you can simply add the assembly to the Bin folder. If you use the Web Application Project model then you should right-click on the project in the Solution Explorer, choose Add References, and browse to the assembly.

The GeneratedImage control assembly (Microsoft.Web.GeneratedImage.dll) also includes a Web control (GeneratedImage), which you can optionally add to the Toolbox in Visual Studio. To do so, right-click within the Visual Studio Toolbox, select Choose Items, and then Browse to the Microsoft.Web.GeneratedImage.dll file on your hard drive. This adds a new control to the Toolbox, GeneratedImage, as the screen shot to the right shows.

Creating a Simple GeneratedImage HTTP Handler


As we discussed earlier, in order to programmatically serve an image we need to create an ASP.NET page or HTTP Handler that's responsible for generating the image. The The GeneratedImage control library includes an HTTP Handler base class named ImageHandler that provides the base functionality for an image serving HTTP Handler. Therefore, we can create an HTTP Handler that derives from this base class and contains the code that generates the image of interest.

Start by adding a new HTTP Handler to your website by choosing to add a new Generic Handler item. (Right-click on the project name or a folder in the Solution Explorer, choose Add New Item, and then select the Generic Handler option from the Add New Item dialog box.) HTTP Handlers generally have the extension .ashx, so name this handler something like MyImageHandler.ashx. Keep in mind that this HTTP Handler is responsible for serving image content and will be cited in <img> elements and ASP.NET Image Web controls. Therefore, make sure that you do not place this file in the App_Code folder, the App_Data folder, or in any other non-accessible location.

HTTP Handlers must implement the IHttpHandler interface; when adding a new HTTP Handler Visual Studio creates a class that implements this interface. However, we want our HTTP Handler to inherit from ImageHandler (which implements IHttpHandler), therefore gut the boilerplate class definition Visual Studio added and replace it with the following:

<%@ WebHandler Language="VB" Class="MyFirstImageHandler" %>

Imports System
Imports System.Web
Imports System.Drawing
Imports Microsoft.Web


Public Class MyFirstImageHandler
   Inherits ImageHandler


   Public Sub New()
      
   End Sub


   Public Overrides Function GenerateImage(ByVal parameters As System.Collections.Specialized.NameValueCollection) As Microsoft.Web.ImageInfo
      
   End Function

End Class

There are three things to note in the above code sample. First, note that I've imported the System.Drawing and Microsoft.Web namespaces. The System.Drawing namespace contains the GDI+ classes for working with images programmatically; the Microsoft.Web namespace contains the GeneratedImage control API, and includes the ImageHandler base class. Next, note that the HTTP Handler class inherits from the ImageHandler class. Finally, note that the body of the HTTP Handler class contains two methods:

  • The Constructor (Public Sub New()) - you can put any initialization logic here, such as the image type generated by the HTTP Handler (JPG, PNG, GIF, etc.)
  • The GenerateImage Method - this method is the workhorse of the HTTP Handler. It is passed a collection of parameter values and must return an ImageInfo object. The ImageInfo object contains the image content to send down to the client.
I've created a series of demo HTTP Handlers and associated ASP.NET pages, which are available for download at the end of this article. One of the HTTP Handlers in the demo is named TextToImageHandler.ashx and uses the GDI+ classes to generate an image from text. As the code below shows, the text that is turned into an image, along with the font settings, colors, and so forth - are hard-coded in the HTTP Handler. In a nutshell, the HTTP Handler below generates an image that displays the text "Hello, World!" in black on a yellow background using the Verdana font, 14pt size and with 5 pixels of horizontal and vertical padding on the top, bottom, left, and right of the text.

<%@ WebHandler Language="VB" Class="TextToImageHandler" %>

Imports System
Imports System.Drawing
Imports System.Web
Imports Microsoft.Web

Public Class TextToImageHandler
   Inherits ImageHandler

   Public Sub New()
      MyBase.ContentType = Imaging.ImageFormat.Png
   End Sub
   
   Public Overrides Function GenerateImage(ByVal parameters As System.Collections.Specialized.NameValueCollection) As Microsoft.Web.ImageInfo
      Dim Message As String = "Hello, World!"
      Dim Font As New Font("Verdana", 14)
      Dim FontColor As Color = Color.Black
      Dim BackColor As Color = ColorTranslator.FromHtml("Yellow")
      Dim HorizontalPadding As Integer = 5
      Dim VerticalPadding As Integer = 5
      
      Return CreateImage(Message, Font, FontColor, BackColor, HorizontalPadding, VerticalPadding)
   End Function
   
   Protected Function CreateImage(ByVal Message As String, ByVal Font As Font, ByVal FontColor As Color, ByVal BackColor As Color, ByVal HorizontalPadding As Integer, ByVal VerticalPadding As Integer) As Microsoft.Web.ImageInfo
      Dim sz As SizeF = Nothing
      
      Using dummyBitmap As New Bitmap(1, 1)
         Dim dummyGraphics As Graphics = Graphics.FromImage(dummyBitmap)
         sz = dummyGraphics.MeasureString(Message, Font)
      End Using
      
      Dim realBitmap As New Bitmap(Convert.ToInt32(sz.Width) + HorizontalPadding * 2, Convert.ToInt32(sz.Height) + VerticalPadding * 2)
      Dim realGraphics As Graphics = Graphics.FromImage(realBitmap)
      realGraphics.Clear(BackColor)
      realGraphics.DrawString(Message, Font, New SolidBrush(FontColor), HorizontalPadding, VerticalPadding)
      
      Return New ImageInfo(realBitmap)
   End Function
End Class

generated as a PNG. The GenerateImage method defines the hard-coded values and then calls the CreateImage method and passes it these hard-coded values. The CreateImage method starts by creating a dummy Bitmap and Graphics object in order to determine how much vertical and horizontal space the text to be displayed will occupy. Once this is known, new Bitmap and Graphics objects are created to render the image. First, a Bitmap object with the appropriate dimensions is created. Next, a Graphics object is created from the Bitmap and its Clear method is used to blanket the background with the specified color. The DrawString method adds the specified textual message to the image and a new ImageInfo object is created based on the Bitmap object and returned.

The image generated by the TextToImageHandler.ashx HTTP Handler. Whenever this HTTP Handler is visited - such as through http://www.yoursite.com/TextToImageHandler.ashx - the page returns a PNG image to the browser with the text "Hello, World!", which is shown to the right. As far as the browser is concerned, this URL is just like any other PNG or JPG or GIF file it would normally request from a web server. You could display this image using an ASP.NET Image Web control and settings its ImageUrl property to TextToImageHandler.ashx.

Another way to display the image dynamically generated by the TextToImageHandler.ashx HTTP Handler is to use the GeneratedImage control, which is a custom compiled Web control. Assuming you added this control to the Toolbox, drag it onto an ASP.NET page and set its ImageHandlerUrl property to the URL of the HTTP Handler. The GeneratedImage control renders into an <img> element referencing the HTTP Handler in the <img> element's src attribute. The following is a screen shot from the ~/Demos/SimpleTextToImageDemo.aspx page, which uses the GeneratedImage control to display the "Hello, World!" image.

The generated image is displayed in an ASP.NET page via the GeneratedImage Web control.

Generating An Image Based On Parameters


The image generated by the TextToImageHandler.ashx HTTP Handler is pretty pointless because it's text, font, and size are all static. If you want to display a static image, why not just create an image file? Using an HTTP Handler is overkill. However, HTTP Handlers are useful for dynamic images, ones whose content is based on external parameters.

Recall that the GenerateImage method is passed a set of parameters. The parameter names and values passed into this list are the collection of querystring parameters included in the call to the HTTP Handler. In other words, if an <img> element requests the HTTP Handler like so:

<img src="TextToImageHandler.ashx?Message=Powered+By+ASP.NET" />

Then when the GenerateImage method is invoked it will have a single parameter in its parameters collection named Message and with the value "Powered By ASP.NET". We can augment the TextToImageHandler.ashx HTTP Handler to base its message, colors, font, and other information on parameters, and doing so only involves fleshing out the GenerateImage method in the example above to read in the values from the parameters collection rather than using hard-coded values. The download at the end of this article includes an HTTP Handler named TextToImageHandler2.ashx that includes this flexibility. The updated GenerateImage method is shown below.

Public Class TextToImageHandler2
   Inherits ImageHandler

   ...
   
   Public Overrides Function GenerateImage(ByVal parameters As System.Collections.Specialized.NameValueCollection) As Microsoft.Web.ImageInfo
      Dim Message As String = "Message parameter not set!"
      If Not String.IsNullOrEmpty(parameters("Message")) Then
         Message = parameters("Message")
      End If
      
      Dim FontStyle As FontStyle = FontStyle.Regular
      If Not String.IsNullOrEmpty(parameters("Bold")) Then
         FontStyle = FontStyle Or Drawing.FontStyle.Bold
      End If
      If Not String.IsNullOrEmpty(parameters("Italic")) Then
         FontStyle = FontStyle Or Drawing.FontStyle.Italic
      End If
      If Not String.IsNullOrEmpty(parameters("Underline")) Then
         FontStyle = FontStyle Or Drawing.FontStyle.Underline
      End If
      If Not String.IsNullOrEmpty(parameters("Strikeout")) Then
         FontStyle = FontStyle Or Drawing.FontStyle.Strikeout
      End If
      
      Dim Font As New Font("Verdana", 14)
      If Not String.IsNullOrEmpty(parameters("Font")) AndAlso Not String.IsNullOrEmpty(parameters("FontSize")) Then
         Font = New Font(parameters("Font"), Convert.ToInt32(parameters("FontSize")), FontStyle)
      End If
      
      Dim FontColor As Color = Color.Black
      If Not String.IsNullOrEmpty(parameters("FontColor")) Then
         FontColor = ColorTranslator.FromHtml(parameters("FontColor"))
      End If
      
      Dim BackColor As Color = Color.White
      If Not String.IsNullOrEmpty(parameters("BackColor")) Then
         BackColor = ColorTranslator.FromHtml(parameters("BackColor"))
      End If
      
      Dim HorizontalPadding As Integer = 5
      If Not String.IsNullOrEmpty(parameters("HorizontalPadding")) Then
         HorizontalPadding = Convert.ToInt32(parameters("HorizontalPadding"))
      End If
      
      Dim VerticalPadding As Integer = 5
      If Not String.IsNullOrEmpty(parameters("VerticalPadding")) Then
         VerticalPadding = Convert.ToInt32(parameters("VerticalPadding"))
      End If
      
      Return CreateImage(Message, Font, FontColor, BackColor, HorizontalPadding, VerticalPadding)
   End Function

   ...
End Class

With this updated HTTP Handler you can create dynamic images with any text, colors, fonts, and padding. As aforementioned, the parameters passed into the GenerateImage method are those that come in through the querystring, but you can supply these parameters programmatically or declaratively via the GeneratedImage Web control, which has a Parameters collection. The ~/Demos/TextToImageDemo.aspx page in the download has a GeneratedImage Web control that has five parameters specified through its declarative markup: Message, Font, FontSize, FontColor, and BackColor.

<cc1:GeneratedImage ID="TextAsImageGenerator" runat="server" ImageHandlerUrl="~/ImageHandlers/TextToImageHandler2.ashx">
   <Parameters>
      <cc1:ImageParameter Name="Message" Value="That that is, is. That that is not, is not." />
      <cc1:ImageParameter Name="Font" Value="Arial" />
      <cc1:ImageParameter Name="FontSize" Value="12" />
      <cc1:ImageParameter Name="FontColor" Value="White" />
      <cc1:ImageParameter Name="BackColor" Value="Navy" />
   </Parameters>
</cc1:GeneratedImage>

The result is the following image.

These parameters can also be set programmatically. The ~/Demos/TextToImageDemo.aspx page also contains a Web Form where a user can enter their own textual message and choose various font and style properties. On clicking "Update Image," the GeneratedImage control's Parameters collection is updated to reflect the user's input, which generates a matching image.

Caching Dynamically Generated Images


By default, each time an image HTTP Handler is requested it executes its GenerateImage method, generates the image, and sends the image contents to the requesting client. This may result in a lot of unnecessary work if the image being generated changes infrequently or does not change for a given set of input parameters. For such scenarios, you may want to cache the image.

The ImageHandler class from which our HTTP Handler inherits, can implement client-side and server-side caching via its EnableClientCache and EnableServerCache properties. Both these properties default to False. To enable caching you need to set them to True, which you can do in the constructor. Enabling client caching instructs the ImageHandler class to set the Response.Cache object's cache-ability to Public and to set the expiry to the TimeSpan value specified by the ImageHandler's ClientCacheExpiration property. In a nutshell, setting EnableClientCache causes the image request to include an HTTP header that tells the browser that it can cache the image for a time specified by the ClientCacheExpiration property (say, ten minutes). That means that if the browser visits a page on the site that references the same image within the next ten minutes, the browser will serve the image from its local cache rather than making a request to the web server for the image.

If you set the EnableServerCache property to True then the ImageHandler class saves a copy of the generated image to the web server's file system. Specifically, these files are saved in the App_Data/_imagecache folder, which is automatically created if it does not already exist. If a request comes in for an image that is cached on disk, the cached copy is sent back to the browser instead of having the image re-generated via the GenerateImage method. The disk-based cache has a hard-coded expiry of five minutes, meaning that after five minutes, the cached image files are deleted.

Keep in mind that the cached image files are specific to the input parameters (if any) passed into the HTTP Handler. For example, if you are using server-side caching and a user makes a request to the TextToImageHandler2.ashx HTTP Handler passing in a Message value of "Hello, World!", the generated image is cached to disk on the server. If another request comes in for that same image within the next five minutes, the cached version is returned. However, if a request comes in to TextToImageHandler2.ashx with a Message value of "To be or not to be", a separate cache instance is created. The same concept applies to client-side caching because the parameters are passed through the querystring and the browser caches based on the full URL, including the querystring.

Conclusion


The GeneratedImage control simplifies serving dynamic images from an ASP.NET website. The project includes both a custom, compiled Web control (GeneratedImage) along with a number of classes that assist in creating an image-serving HTTP Handler. In this article we saw how to create an HTTP Handler to serve images that extends the ImageHandler class, as well as how to use the GeneratedImage Web control to display images generated by these HTTP Handlers.

One feature of this project that we have not yet explored is its ability to transform images. With a single line of code you can apply an image transform class to the dynamically-generated image. The project ships with a single image transform class that resizes an image, but you can create your own image transform classes to add watermarks, rotate images, and perform other common transforms. This functionality is explored in a follow-up article, Image Transforms with the ASP.NET Generated Image Control.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the Demo Code Used in this Article

    Further Reading:


  • Download the GeneratedImage Control
  • ASP.NET Futures - Generating Dynamic Images With HttpHandlers Gets Easier
  • Working with the New ASP.NET GeneratedImage Control
  • Storing Binary Files Directly in the Database Using ASP.NET 2.0
  • Data Access Tutorials :: Working with Binary Files
  • How ASP.NET Web Pages are Processed on the Web Server
  • Serving Dynamic Content with HTTP Handlers
  • Image Transforms with the ASP.NET Generated Image Control


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article