|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Table of Contents
IntroductionAs 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:
Before we jump into coding, a little background… BackgroundMSDN 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.
White Box AnalysisThis 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 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 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 Now, create a new class and name it 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 ' 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
The main entry point for the Prior to the execution of the /// <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 All that the Based on the successful execution of the /// <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 The thumbnailer.ashx?img=im/ImageSource.jpg&size=72
As these parameters suggest, /// <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 /// <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 Next, the /// <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 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
Since an HTTP handler does not have access to the The following code snippet illustrates the retrieval of the default image from the assembly's embedded resources: /// <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 /// <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 The parameter of interest here is the Why create a method matching this delegate that does nothing and pass a reference to this delegate as the third parameter of 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: 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 Black Box AnalysisWhether 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.
CaveatsAccurately named, this section provides warnings, cautions and/or issues to look out for when using the Thumbnailer HTTP handler.
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
References
ConclusionWell, 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
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||