Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Thumbnailer HTTP Handler

4.84/5 (37 votes)
26 Oct 200617 min read 5   2.8K  
Create dynamic image thumbnails on-the-fly at runtime.

Thumbnailer - Demo Screenshot

Table of Contents

Introduction

As developers, we are often constrained by our environment (e.g., physical and virtual memory, and hard disk space), project requirements (e.g., timelines/deadlines, resources, and software quality), and customer expectations. While there is no clear 'winner' in the battle of constraints, this article focuses on the environmental constraints: specifically -- hard disk restrictions.

To help set the scene for the article, let me start with the earlier phases of the Software Development Life Cycle (SDLC) and define the problem and a few of the respective requirements.

The system that I was building needed to provide an interface for its users to upload files including images – a pretty common requirement. Another common requirement was to display the uploaded images in an image gallery-like interface that would render thumbnail images of the respective uploaded images. Yet another requirement – uncommon – was the need to minimize (as much as possible) the number of physical files on the data store to conserve hard disk space – the aforementioned hard disk constraint and problem scope.

The implementation I choose was to create an image optimizer control that would scale and optimize the quality of the original uploaded image and store the results to a data store (in this case a hard disk), and create an HTTP handler that would create thumbnail images of the optimized images on the fly, at runtime, and in memory (no physical disk space to store the temporary thumbnail file). While the image optimizer is a subject for another day; this article focuses on the development of a thumbnail creation HTTP handler – Thumbnailer HTTP handler (THH).

This article is organized into two primary modules:

  • White Box Interface – the internal logic and workings of the Thumbnailer HTTP handler. This section is for those developers that are interested in the development of the Thumbnailer HTTP handler.
  • Black Box Interface – how to interface with (use) the Thumbnailer HTTP handler. This section is for those developers that what to interface with the Thumbnailer HTTP handler for the functionality that it provides.

Before we jump into coding, a little background…

Background

MSDN defines an HTTP handler as: "a process (frequently referred to as the 'endpoint') that runs in response to a request made to an ASP.NET Web application. The most common handler is an ASP.NET page handler that processes .aspx files. When users request an .aspx file, the request is processed by the page via the page handler." Other common HTTP handlers are the Web service handler (*.asmx extension), ASP.NET user control handler (*.ascx extension), and the Trace handler (trace.axd).

To understand what an HTTP handler is, you should understand the processing of an ASP.NET request – commonly referred to as the ASP.NET HTTP pipeline. While a discussion of the ASP.NET pipeline could consume quite a few pages (if not a book) on its own, the diagram below briefly illustrates a common HTTP request. Once an ASP.NET resource is requested (from the client through IIS), the request passes through a series of HTTP modules and terminates at the HTTP handler. Next, the HTTP handler does its processing magic (based on the HTTP handler's logic) and produces output in the form of a response (otherwise known as the HTTP response). The response then reverses its incoming process and passes back though the HTTP modules, etc… and terminates at the client. For more information on the ASP.NET pipeline and HTTP modules, handlers, requests, and responses, please refer to the .NET SDK on MSDN or search the topic on Google.

Thumbnailer - ASP.NET Pipeline

White Box Analysis

This is not a comprehensive analysis on HTTP handlers; neither is it a 'how to' on interfaces and abstract classes, nor is it a journal on software engineering theories. However, what this article is is a focused discussion on the development of a thumbnail creation HTTP handler. Although, I will provide external links to this information, so if you are intrigued to continue your understanding of the subject(s) for professional development, you will have a handful of references to start with.

To create a custom HTTP handler, you must implement the System.Web.IHttpHandler interface. However, once you start creating a few HTTP handlers you will immediately recognize common patterns that could be extracted to create a more universal/generic HTTP handler and use this newly create HTTP handler as the base class for your custom HTTP handlers. Well, this is exactly what Scott Hanselmann did. He created a boilerplate class that aggregates the common patterns of HTTP handlers. Then, Phillip J Haack further extended Scott's work and created an abstract HTTP handler class – BaseHttpHandler. Object-oriented programming (OOP) is great, and when you consolidate smart minds to create reusable and extendable objects, you solidify the benefits that OOP brings to the table. Great work guys!

Needless to say, this is the class that I use to create all of my Http handlers. So let's get started…

Open Visual Studio 2005, create a new project of type 'class library', name the project anything you want (I named mine Shp.Handler), and delete the default Class1.cs file. Now, download the BaseHttpHandler abstract class from Phillip J Haack's site or from the source provided with this article, and add it to you your project.

If at this point you try to compile the class library, you will receive a list of errors. You need to add a reference to the System.Web assembly, and while you're at it, add a reference to the System.Drawing assembly, you will need this reference later. If you don't know how to add a reference to your project, please view "How to: Add or Remove References in Visual Studio" on MSDN.

Now, create a new class and name it Thumbnailer (or anything else you want). Extend this new class from the abstract BaseHttpHandler class and implement its abstract members. Also, add using references to the following namespaces: System.Web, System.Web.UI, System.Drawing, System.Drawing.Imaging, System.IO, and System.Reflection. For more information on these namespace, please refer to the .NET SDK documentation on MSDN. At this point, your class should look something like this:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Drawing.Imaging;
using System.Reflection;
using System.Drawing;

namespace Shp.Handler
{
  class Thumbnailer : BaseHttpHandler
  {
    /// <summary>
    /// Gets a value indicating whether this handler
    /// requires users to be authenticated.
    /// </summary>
    /// <value>
    ///    <c>true</c> if authentication is required
    ///    otherwise, <c>false</c>.
    /// </value>
    public override bool RequiresAuthentication
    {
      get { throw new Exception("The method or operation" + 
                                " is not implemented."); }
    }

    /// <summary>
    /// Gets the MIME Type.
    /// </summary
    public override string ContentMimeType
    {
      get { throw new Exception("The method or operation" + 
                                " is not implemented."); }
    }

    /// <summary>
    /// Handles the request.
    /// This is where you put your business logic.
    /// </summary>
    protected override void HandleRequest (HttpContext context)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Validates the parameters. Inheriting classes
    /// must implement this and return 
    /// true if the parameters are valid, otherwise false.
    /// </summary>
    /// <param name="context">Context.</param>
    /// <returns><c>true</c> if the parameters are valid;
    /// otherwise, <c>false</c></returns>
    public override bool ValidateParameters (HttpContext context)
    {
      throw new Exception("The method or operation " + 
                          "is not implemented.");
    }
  }
}

Adding comments to you code is a good, albeit often overlooked, habit to get yourself into, so in the interest of practicing good programming conventions, I've decorated the implemented members of the aforementioned code. These comments are similar to the comments from the base class, so you could have just as easily used the '<see cref="">' comment tag to reference the respective BaseHttpHandler abstract class member.

Because object-oriented programming in non-procedural, it is difficult (if not impossible) to explain an object's execution flow in a step-wise manner without knowledge of the respective classes' members. Therefore, below is a class diagram of the final Thumbnailer class to assist with understanding the classes' execution flow:

Thumbnailer - Class Diagram

The main entry point for the Thumbnailer HTTP handler is the HandleRequest method. This method takes an HttpContext object as its only parameter. The HttpContext object encapsulates all HTTP-specific information about the respective request; therefore, it provides access to the HTTPRequest object and its members including the QueryString collection. However, there are few properties and methods that the base class (BaseHttpHandler) requires to be set/executed before the HandleRequest method is invoked.

Prior to the execution of the HandleRequest method, the base class calls/validates parameters, determines if authentication is required, and if authentication is required, checks to see if the current user is authenticated. The Thumbnailer class is responsible for providing this information to its base via the following two overridden members:

C#
/// <summary>
/// Gets a value indicating whether this handler
/// requires users to be authenticated.
/// </summary>
/// <value>
///    <c>true</c> if authentication is required
///    otherwise, <c>false</c>.
/// </value>
public override bool RequiresAuthentication
{
  get { return false; }
}

/// <summary>
/// Gets the MIME Type.
/// </summary>
public override string ContentMimeType
{
  get { return this._mimeText; }
}

In this case, the RequiresAuthentication property just returns false because the resource in question (the source image) does not require authentication. If your resource required authentication, you could easily add the necessary logic to the Thumbnailer class or just return true. The BaseHttpHandler class takes care of the authentication service by checking the current user's Identify object's IsAuthenticated property. Please refer to the .NET SDK documentation on MSDN for more information.

All that the ValidateParameters method does is return a bool specifying if the parameters are valid (true) or not (false). In most cases, the HTTP handler would evaluate the incoming parameters and return false if either one of these parameters are null or empty, otherwise return true. However, the Thumbnailer implementation will always return true. If the parameters are null or empty, Thumbnailer will return a default thumbnail image from an embedded image resource (more on this later).

Based on the successful execution of the RequiresAuthentication and ValidateParameters methods and additional processing (if necessary, e.g. authentication), the base class will invoke the HandleRequest method of the Thumbnailer HTTP handler. This is where the rubber meets the road.

C#
/// <summary>
/// Main interface for reacting to the Thumbnailer request.
/// </summary>
protected override void HandleRequest (HttpContext context)
{
  if (string.IsNullOrEmpty(
      context.Request.QueryString[SIZE_PARAM]))
    this._sizeType = ThumbnailSizeType.Small;
  else
    this.SetSize(context.Request.QueryString[SIZE_PARAM]);

  if ((string.IsNullOrEmpty(
       context.Request.QueryString[IMG_PARAM])) ||
   (!this.IsValidImage(context.Request.QueryString[IMG_PARAM])))
  {
    this.GetDefaultImage(context);
  }
  else
  {
    string file = 
      context.Request.QueryString[
      IMG_PARAM].Trim().ToLower().Replace("\\", "/");
    if (file.IndexOf("/") != 0)
          file = "/" + file;

    if (!File.Exists(context.Server.MapPath("~" + file)))
      this.GetDefaultImage(context);
    else
    {
      using (System.Drawing.Image im = 
             System.Drawing.Image.FromFile(
             context.Server.MapPath("~" + file)))
      using (System.Drawing.Image tn = 
             this.CreateThumbnail(im))
      {
        tn.Save(context.Response.OutputStream, 
                this._formatType);
      }
    }
  }
}

The HandleRequest acquires and validates the incoming parameters, generates the requested thumbnail, and return the thumbnail using the Response object's OutputStream property.

The Thumbnailer acquires the following incoming parameters via the Request object's QueryString collection property: img and size. The following is a sample of the incoming request that the Thumbnailer is registered to handle:

thumbnailer.ashx?img=im/ImageSource.jpg&size=72

As these parameters suggest, img is the local path to the image source file and size is an enumerated value stating the size of the requested thumbnail. When evaluated, the size parameter is transformed into a ThumbnailSizeType enumeration:

C#
/// <summary>
/// An internal enumeration defining the thumbnail sizes.
/// </summary>
internal enum ThumbnailSizeType
{
  Small = 72,
  Medium = 144,
  Large = 288
}

The value associated with each enumeration name represents the maximum width and/or height (in pixels) of the requested thumbnail.

The HandleRequest method checks to see if the size parameter exists. If it does not exist, it will set a default size of small (ThumbnailSizeType.Small). If it does exist, HandleRequest will invoke the SetSize helper method.

C#
/// <summary>
/// Sets the size of the thumbnail base on the size parameter.
/// </summary>
/// <param name="size">The size parameter.</param>
private void SetSize (string size)
{
  int sizeVal;
  if (!Int32.TryParse(size.Trim(), 
      System.Globalization.NumberStyles.Integer, 
      null, out sizeVal))
    sizeVal = (int)ThumbnailSizeType.Small;

  try
  {
    this._sizeType = (ThumbnailSizeType)sizeVal;
  }
  catch
  {
    this._sizeType = ThumbnailSizeType.Small;
  }
}

This method will evaluate the size parameter and set the class variable _sizeType of type ThumbnailSize. I use a try/catch statement to ensure that the request contains a valid size value.

Next, the HandleRequest method checks to see if the img parameter is null or empty. If the parameter is null or empty, a default thumbnail is retrieved from the assembly as an embedded resource (more on this later), if the parameter is neither null nor empty, the IsValidImage helper method is invoked.

C#
/// <summary>
/// Determines if the img parameter is a valid image.
/// </summary>
/// <param name="fileName">File name from
///        the img parameter.</param>
/// <returns>
///   <c>true</c> if valid image, otherwise <c>false</c>
/// </returns>
private bool IsValidImage (string fileName)
{
  string ext = Path.GetExtension(fileName).ToLower();
  bool isValid = false;
  switch (ext)
  {
    case ".jpg":
    case ".jpeg":
      isValid = true;
      this._mimeText = "image/jpeg";
      this._formatType = ImageFormat.Jpeg;
      break;
    case ".gif":
      isValid = true;
      this._mimeText = "image/gif";
      //this._formatType = ImageFormat.Gif;
      this._formatType = ImageFormat.Jpeg;
      break;
    case ".png":
      isValid = true;
      this._mimeText = "image/png";
      //this._formatType = ImageFormat.Png;
      this._formatType = ImageFormat.Jpeg;
      break;        
    default:
      isValid = false;
      break;
  }
  return isValid;
}

This method evaluates the incoming img parameter based on the parameter's extension to determine the class member variable of type ImageFormat and the overridden member ContentMimeType. ImageFormat is an enumeration of the System.Drawing.Imaging namespace. It specifies the format of the image. Whereas, ContentMimeType is a string that will ultimately determine the Response object's ContentType (the MIME type of the output stream). Finally, IsValidSize will return true or false based on the img parameter's validation, and if it returns false, like the null img parameter, the Thumbnailer will use a default thumbnail retrieved from the assembly as an embedded image resource.

Embedded resources are commonly used by custom control designers to embed client-side scripts and images directly into the control's assembly. Embedding resources in assemblies helps maintain resource and code integrity/encapsulation on deployment. When an assembly is added to a project (e.g. Web site) that requires external files (scripts, images, etc…), embedded resources provide a one-stop approach. The developer does not have to add the required external resources to the client application as separate external files. When an assembly with embedded resources is added, the external resources are maintained and managed by the assembly itself, so issues like script registration are not necessary.

The reason why I choose to add embedded resources to the Thumbnailer's assembly is for default image rendering. When a request is made for a resource that the Thumbnailer is registered to service and the request does not contain the required parameters, the Thumbnailer will render a default image embedded in its assembly.

Thumbnailer - Image Not Found

Since an HTTP handler does not have access to the Page object, we need to access resources directly via reflection rather than using the incredibly easy ClientScriptManager class (via the Page.ClientScript property).

The following code snippet illustrates the retrieval of the default image from the assembly's embedded resources:

C#
/// <summary>
/// Get default image.
/// </summary>
/// <remarks>
/// This method is only invoked when
/// there is a problem with the parameters.
/// </remarks>
/// <param name="context"></param>
private void GetDefaultImage (HttpContext context)
{
  Assembly a = Assembly.GetAssembly(this.GetType());
  Stream imgStream = null;
  Bitmap bmp = null;
  string file = string.Format("{0}{1}{2}", 
                DEFAULT_THUMBNAIL, 
                (int)this._sizeType, ".gif");

  imgStream = a.GetManifestResourceStream(a.GetName().Name + file);
  if (imgStream != null)
  {
    bmp = (Bitmap.FromStream(imgStream) as Bitmap);
    bmp.Save(context.Response.OutputStream, this._formatType);

    imgStream.Close();
    bmp.Dispose();
  }
}

For more information on accessing, manipulating, and retrieving resources from an assembly, please refer to the .NET SDK documentation on MSDN.

If all goes well with the incoming parameter logic, the HandleRequest method will invoke the CreateThumbnail method. This method takes the Image source object as the single parameter, generates scaling variables based on the Thumbnailer incoming size parameter, and invokes the Image object's GetThumbnialImage method.

C#
/// <summary>
/// This method generates the actual thumbnail.
/// </summary>
/// <param name="src"></param>
/// <returns>Thumbnail image</returns>
private System.Drawing.Image 
        CreateThumbnail (System.Drawing.Image src)
{
  int maxSize = (int)this._sizeType;

  int w = src.Width;
  int h = src.Height;

  if (w > maxSize)
  {
    h = (h * maxSize) / w;
    w = maxSize;
  }

  if (h > maxSize)
  {
    w = (w * maxSize) / h;
    h = maxSize;
  }

  // The third parameter is required and is
  // of type delegate.  Rather then create a method that
  // does nothing, .NET 2.0 allows for anonymous
  // delegate (similar to anonymous functions in other languages).
  return src.GetThumbnailImage(w, h, 
         delegate() { return false; }, IntPtr.Zero);
}

The GetThumbnailImage method takes four parameters: int::width; int::height; bool::Image.GetThumbnailImageAbort (delegate); and IntPtr.

The parameter of interest here is the GetThumbnailImageAbort. This parameter is required; however, the .NET SDK documentation states that "the delegate is not used." I'm just the messenger, not the designer :-)! GetThumbnailImageAbort delegate's signature takes zero parameters and returns a bool.

Why create a method matching this delegate that does nothing and pass a reference to this delegate as the third parameter of GetThumbnailImageAbort method? I cannot answer that; however, what I can do is provide an elegant solution to implement the required delegate using .NET 2.0 Anonymous Methods.

Prior to .NET 2.0, you had to create a method that matches a delegate's signature and pass a reference to this method for use when the delegate was invoked. You can still implement it this way, but with anonymous methods we have a much more elegant solution. Rather then creating and referencing a bunch of unnecessary objects/methods, we can just pass a block of code as the delegate's parameter. This is exactly what I am doing with the following code snippet:

C#
return src.GetThumbnailImage(w, h, delegate()
       { return false; }, IntPtr.Zero);

Note that the third parameter's signature matches the required delegate's signature: zero parameters, returning a boolen

Black Box Analysis

Whether you read the previous Thumbnailer White Box Analysis or skipped directly to this section, this section deals strictly with getting the Thumbnailer configured and into production.

  1. The easiest way to use Thumbnailer is download this article's code, open the Shp.Handler solution file, and compile the code. The solution consists of two projects:
    • Shp.Handler: the Thumbnail HTTP handler's project
    • Shp.Handler.Web: the testing Web Site project.
  2. If you are creating your own Web Site project, you will need to import the Shp.Handler assembly by using the 'Add Reference' utility.
  3. Using Shp.Handler.Web or your own Web Site, add a new Web Form.
  4. For this example, we will use a standard ASP.NET data Repeater control. Add a data Repeater control, switch to source (HTML) view, add an ItemTemplate, and add a standard ASP.NET Image control to the ItemTemplate. Your code should look something like the following snippet:
    XML
    <asp:Repeater ID="Repeater1" runat="server">
      <ItemTemplate>
        <asp:Image ID="Image1" runat="server" />
      </ItemTemplate>
    </asp:Repeater>
  5. We are going to use local images within the application's root directory, so add an image source directory within the application's root directory, and add a few JPEGs. If you are using the demo project, this directory and a few images are already present in /im/.
  6. To simplify the code as much as possible, we are going to use a list of source image files as the data source, rather then create a custom data source and access the data source via custom business logic. So, open the code-behind file and in the Page's Load event handler add the following code:
    C#
    protected void Page_Load (object sender, EventArgs e)
    {
        IList<FileInfo> files = new List<FileInfo>();
        string filters = "*.jpg;*.png;*.gif";
    
        foreach (string filter in filters.Split(';'))
        {
          FileInfo[] fit = new DirectoryInfo(
                     this.Server.MapPath("~/im")).GetFiles(filter);
          foreach (FileInfo fi in fit)
          {
            files.Add(fi);
          }
        }    
        this.Repeater1.DataSource = files;
        this.Repeater1.DataBind();
    }
    All that this method does is acquire all image files using a filter for image types, adds the images to a generic List object of type FileInfo, and uses the List as the Repeater's data source. Of course, don't forget to bind the Repeater to the data source.
    Note: If you are new to the FileInfo object, take the time to look it up on MSDN. We will be using some of the FileInfo's properties for declarative data binding.
  7. Switch back the source (HTML) view. For this example, we will wrap the ASP.NET Image control (previously added) within a standard HTML anchor control, and declaratively data bind the HTML anchor element's href attribute and the Image control's ImageUrl property to the FileInfo's Name property. Adding a bit of style and formatting, our entire source (HTML) view will look like the following:
    ASP.NET
    <%@ Page Language="C#" AutoEventWireup="true" 
             CodeFile="Default.aspx.cs" Inherits="_Default" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head id="Head1" runat="server">
    <title>Thumbnailer Test</title>
    <style>
      .img_tn_cntr_72 {height: 84px; vertical-align: 
                       middle; display: inline;}
      .img_tn_cntr_144 {height: 156px; vertical-align: 
                        middle; display: inline;}
      .img_tn_cntr_288 {height: 300px; vertical-align: 
                        middle; display: inline;}
      .img_tn {padding: 5px; border: 
               1px solid #000000; margin: 5px;}
    </style>
    </head>
    <body>
    <form id="form1" runat="server">
    <div>
    <asp:Repeater ID="Repeater1" runat="server" 
         OnItemDataBound="Repeater1_ItemDataBound">
    <ItemTemplate>
    <div class="img_tn_cntr_72">
    <a href='<%# Eval("Name", "im/{0}") %>' target="_blank">
    <asp:Image ID="Image1" runat="server" 
      ImageUrl='<%# Eval("Name", "ImageThumbnailer.ashx?img=im/{0}&size=72") %>'
      CssClass="img_tn" BorderColor="black" 
      BorderWidth="1px" BorderStyle="solid" /></a>
    </div>
    </ItemTemplate>
    </asp:Repeater> 
    </div>
    </form>
    </body>
    </html>

    Notice the Name parameter in the in the Eval declarative data binding statement. This is the Name property of the FileInfo class. Name returns the name of the image with the file extension.

    Note: Don't be concerned with the Repeater's ItemDataBound event. I just use this method to add custom rollover behavior. The code is easy to understand, and if you want to review it, please view the code-behind for the page in the article's download.

    Take note of the following code snippet:

    thumbnailer.ashx?img=im/ImageSource.jpg&size=72
    This looks and acts like a standard Query String; however, rather then communicating with a Page, this statement communicates with the Thumbnailer HTTP handler: the client interface to the Thumbnailer HTTP handler. thumbnailer.ashx is registered with the application in the Web.Config (see next step). The two parameters are img (the relative path to the image source) and size (the size of the thumbnail). The size parameter is actually transformed into an enumeration that represents 72, 144, and 288 pixels; therefore, this parameters options are 72, 144, and 288.
  8. Okay, we're almost ready to build and run the application. The last step is to register the Thumbnailer HTTP handler with the application. Open up the Web.Config file and add the following as a child element to the system.web element:
    XML
    <httpHandlers>
      <add verb="*" path="ImageThumbnailer.ashx" 
           type="Shp.Handler.Thumbnailer, Shp.Handler"/>
    </httpHandlers>
    The <httpHandlers> element 'maps incoming requests to the appropriate handler according to the URL and the HTTP verb that is specified in the request' (MSDN). In this case, the handler is Thumbnailer specified by the type attribute and the URL is 'ImageThumbnailer.ashx' specified by the path attribute. For more information on this and other ASP.NET configuration settings, please see the .NET SDK documentation on MSDN.
  9. Build and run your Web application. Your browser should render content similar to the screenshot at the beginning of this article.

Caveats

Accurately named, this section provides warnings, cautions and/or issues to look out for when using the Thumbnailer HTTP handler.

  • When I was developing Thumbnailer and attempting to create thumbnails for *.bmp and *.png files and calling the Image.Save method, a generic GDI exception was raised. To avoid this issue, I removed support for *.bmp images and rather then create thumbnails of the same type as the source image, all thumbnails are created as *.jpg files. Besides, for most situations where thumbnails are required, the source image will more likely then not be a *.jpg/*.jpeg file, so you may never run into similar issues.
  • The included sample Web application does not render the "No Image Found" image generated by the Thumbnailer HTTP handler, because all source images are valid. However, if we were to add an invalid file/image, the "No Image Found" image would render as a default thumbnail, embedded within the Thumbnailer's assembly, for invalid files. This will be an issue within a Web application similar to the sample Web application because when the thumbnail is clicked the source image is rendered in a popup; however, in the case of the "No Image Found" image, there is no local source image. When using the Thumbnailer in this type of situation, you must handle this issue within the client Web application, otherwise ASP.NET will throw an exception.

Note: If you did not read 'White Box Analysis' and the "No Image Found" issue is unclear, please review the 'White Box Analysis' for more information.

Taking It Further

  • Create a full blown Image Gallery application.
  • Add Microsoft AJAX (formerly ATLAS) to display a source image on clicking the thumbnail.
  • Add support for other files.

References

Conclusion

Well, as this article comes to an end, I hope that you have benefited and found some value with the Thumbnailer HTTP handler.

The world of opportunity with HTTP handlers and their counterpart HTTP modules is enormous and as broad as your imagination can take them. Creating logic outside the standard ASP.NET interfaces (*.aspx, *.ascx, *.asmx) can be very powerful and increase developer efficiency with respect to make-once-use-many object approach to software engineering.

Above all, I have learned to respect the hard work that goes into creating the universe of valuable online articles and references. If you haven't done so yet, I challenge you write an article on a topic that you've learned and that you feel would be of value to the user community, and publish it.

If you have any feedback on the quality of this article (or lack thereof), please let know. I'm always eager to learn from others.

Until next time, Go Noles…

Revision History

  • Initial version, v1.0, 23 OCT 2006

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here