ASP.NET Under the Hood
LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 2.0
Implementation Patterns for HTTP Handlers
Extend ASP.NET Apps with Custom HTTP Handlers
By Michele Leroux Bustamante
Greetings ASP.NET architects and developers! Join me in another fun-filled installment of ASP.NET Under the Hood as we unravel some ASP.NET mysteries!
Q. I read your April column that discussed implementing a custom HTTP module for guest authentication; could you talk about HTTP handlers next, and possibly explain the different reasons for implementing a custom handler?
A. ASP.NET extensibility is definitely one of my favorite topics. Although it has been covered before, I ll take this opportunity to review how HTTP handlers are engaged in processing requests, and summarize some of the implementation patterns that best fit the creation of custom handlers.
How Does ASP.NET Process Requests?
The first step in understanding HTTP handlers is to understand how the ASP.NET runtime processes all requests through a handler! IIS engages the ASP.NET runtime if the request is for an extension that is mapped to ASP.NET. Figure 1 illustrates this configuration for a Web site or virtual directory in IIS.
Figure 1: IIS maps file extensions
to ISAPI extensions like the aspnet_isapi.dll.
In the case of IIS 5.0, the IIS runtime instance (inetinfo.exe) is responsible for sending the request to ASP.NET over a named pipe (IPC) channel. In the case of IIS 6.0, the http.sys kernel receives all incoming requests to IIS and uses inetinfo.exe only as a metabase for mapping the request to the correct application pool. This improves reliability and performance ... but I digress ... we re talking about what happens after ASP.NET gets the request! In either case (IIS 5.0 or 6.0), after ASP.NET receives the request stream, it passes it to a particular HTTP handler based on the application configuration.
For each request the
"System.Web.UI.PageHandlerFactory"/> "System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, .../> "System.Web.HttpForbiddenHandler"/> "System.Web.HttpForbiddenHandler"/> "System.Web.HttpForbiddenHandler"/> The default configuration (machine.config) provides
settings for all applications, however it can. Any component that implements
the IHttpHandlerFactory or IHttpHandler interface can be configured in this
section. Ultimately, the request is handled by an HTTP handler, and the factory s
job is to return the appropriate handler for the type of request. For example,
the PageHandlerFactory is responsible for creating a particular Page object
based on the .aspx page requested. Each Page object behind a Web form
implements IHttpHandler! To put the job of the HTTP handler factory and HTTP
handler into another perspective, Figure 2 illustrates the sequence of application
events around the execution of a handler. As soon as the runtime determines
that the requested page output has not been cached, the handler is created. If
a factory is configured for the request type, the factory is delegated this
responsibility and must return a valid IHttpHandler object from the GetHandler
operation. Every handler implements a ProcessRequest method, and this is where
all the goodness happens to provide a meaningful response. After processing the
request, the request lifecycle winds down with some post-processing events that
may end up in caching the page output or some other detail before the request
completes. In short, the HTTP handler does all the work, depending on
the request. When you create a custom handler you are looking to provide some
form of custom processing beyond what we get for free with ASP.NET pages, Web
services, and other configured resources. In the rest of this article I ll
illustrate creating a custom HTTP handler, or handler factory as appropriate,
and discuss some useful scenarios for their implementation. HTTP handler factories have one core function: to
instantiate an HTTP handler according to the request. They implement the
interface IHttpHandlerFactory from the System.Web namespace: public interface IHttpHandlerFactory { IHttpHandler
GetHandler(HttpContext context, string requestType,
string url, string pathTranslated); void
ReleaseHandler(IHttpHandler handler); } ASP.NET passes enough information to the GetHandler
operation so that the factory can determine which HTTP handler to instantiate
and return to the runtime for later use. HTTP handlers implement the interface IHttpHandler, also
from the System.Web namespace: public interface IHttpHandler { void
ProcessRequest(HttpContext context); bool IsReusable { get; } } As mentioned earlier, ProcessRequest is called by the
ASP.NET runtime, so this is where the handler does its work. Ultimately, its
job is to process the incoming request stream and return a meaningful response
to the response stream before returning to the runtime to complete the request
cycle. To configure a custom handler factory or handler, you add entries to the
In its simplest form, a handler is an endpoint that allows
you to process a request and send a response. The most common endpoints for a Web
application are pages (.aspx) and Web services (.asmx), but they also contain
significant processing logic for their respective roles. Sometimes you just
want to expose an endpoint that will allow you to generate a custom response
without the overhead. This is where implementing a simple handler using the
.ashx extension comes in handy. You can add a new .ashx resource to your Web application
using the Generic Handler template, as shown in Figure 3. This adds an endpoint with the extension .ashx to your
project, with the <%@ WebHandler %> directive indicating it s a handler.
The class associated with the endpoint implements IHttpHandler. That means the
output for invoking this .ashx endpoint is, well, whatever you want to put into
the ProcessRequest functionality! The main driver for using a Web handler instead of a Page
object is to provide content that is not page-centric. For example, to format
images presented to the browser in a picture frame, you might want to provide a
friendly URL like this: http://localhost/HttpHandlers/ShowImage.ashx?myphoto.
ProcessRequest for this endpoint can take the query string, find the associated
image, and present it in a framed table layout. The code for this
implementation is shown in Figure 4. (The Generic Handler template inserts an
inline implementation of the IHttpHandler class, as shown in Figure 4. It is
preferable to put the class into a code-behind file, as you ll see in the C#
version of the sample code.) <%@ WebHandler Language="VB"
Class="ShowImage" %> Imports System Imports System.Web Public Class ShowImage : Implements IHttpHandler Public Sub
ProcessRequest(ByVal context As HttpContext) Implements
IHttpHandler.ProcessRequest context.Response.ContentType = "text/plain" context.Response.Write(" If
context.Request.QueryString.Count = 0 Then Throw New
HttpException("Page not accessible, must provide a query
string") End If Dim qs As String =
context.Request.QueryString(0) context.Response.Write(" 'Images/framebig.JPG'
width=540 height=568> context.Response.Write("") End Sub Public ReadOnly Property
IsReusable() As Boolean Implements
IHttpHandler.IsReusable Get Return False End Get End Property End Class Figure 4: Take the
query string, find the associated image, and present it in a framed table
layout. Web handlers like this can also be leveraged behind the
scenes to format sections of page output. For example, a page that displays
photos normally provides an tag that references the relative server
path to the actual image, as shown here: If you wanted the image to be formatted with a watermark,
you can wrap the functionality in a Web handler and reference it from the src
attribute, like this: The handler should write the formatted content directly to
the output stream, as shown here: Dim file As String = context.Request.MapPath(qs) Dim bmp As Bitmap = GraphicUtility.GenerateWatermark(file, "SAMPLE",
WatermarkPosition.Middle) context.Response.ContentType = "image/Jpeg" bmp.Save(context.Response.OutputStream, Imaging.ImageFormat.Jpeg) So, simple handlers like .ashx are very useful for
providing new endpoints that supply content to the browser as a public
endpoint, or as a private endpoint that wraps how specific content is emitted. Simple handlers have their place, but there are more
powerful ways to leverage handlers. You can also create handler components that
are configurable behind the scenes (no .ashx extension required) to
automatically handle specific extension mappings. For example, if you want all
images from the /ProtectedPhotos directory to be watermarked, you can configure
a handler for that subdirectory to perform that function automatically. This
requires a few steps: If you open the IIS console from the Control Panel and
expand the Web Sites node, you should be able to find the virtual directory for
the sample application, named HttpHandlers. Right-click on this node and select
Properties; from the Directory tab select Configuration. From here you can add
a new extension that maps to
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll (check the path
on your machine) as shown in Figure 5. As for creating the WatermarkImageHandler, you can add a
class to the project and implement IHttpHandler. Add the code to watermark an
image in the ProcessRequest operation, as shown in Figure 6. Imports System Imports System.Web Imports System.Drawing imports System.Drawing.Imaging Imports Microsoft.VisualBasic Public Class WatermarkImageHandler Implements IHttpHandler Public Sub
ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest Dim response As
HttpResponse = context.Response Dim request As
HttpRequest = context.Request Try Dim imageToRender As
String = request.FilePath Dim effect As
GraphicEffect = GraphicEffect.Watermark Dim myFormat As
ImageFormat If
imageToRender.IndexOf(".gif") <> -1 Then myFormat =
ImageFormat.Gif ElseIf
imageToRender.IndexOf(".jpg") <> -1 Then myFormat =
ImageFormat.Jpeg Else Throw New Exception("Invalid
image format!") End If Dim imageFile As
String = context.Server.MapPath(imageToRender) Dim objBitmap As
Bitmap = GraphicUtility.GenerateWatermark(imageFile,
"SAMPLE", WatermarkPosition.Middle) objBitmap.Save(response.OutputStream,
myFormat) response.Flush() Catch ex As Exception context.Trace.Write(ex.Message) End Try End Sub Public ReadOnly Property
IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return True End Get End Property End Class Figure 6: Add code
in the ProcessRequest operation to watermark an image. This handler assumes that the request is being processed
for images with the extension .gif or .jpg, and handles reading the image from
disk, watermarking the image, then writing the image to the response stream. For
this to work, ASP.NET needs to load the handler when .gif or .jpg files are
requested. The following code configures WatermarkImageHandler only for the
/ProtectedPhotos subdirectory so that only those images are affected by the
request: "WatermarkImageHandler"/> "WatermarkImageHandler"/> As mentioned earlier, you can tell ASP.NET to use a
handler or handler factory in the The code in Figure 7 illustrates an IHttpHandler
implementation that uses the HttpContext to determine if the user has been
authenticated. If not, the WatermarkImageHandler is created. If so, the
StaticImageHandler is created. Imports System Imports System.Web Public Class ImageHandlerFactory Implements
IHttpHandlerFactory Public Function
GetHandler(ByVal context As HttpContext, ByVal requestType As String, ByVal url As
String, ByVal pathTranslated As
String) As IHttpHandler Implements IHttpHandlerFactory.GetHandler Dim handler As Object =
Nothing If
context.User.Identity.IsAuthenticated Then handler = New
StaticImageHandler() Else handler = New
WatermarkImageHandler() End If Return handler End Function Public Sub
ReleaseHandler(ByVal handler As IHttpHandler) Implements
IHttpHandlerFactory.ReleaseHandler Return End Sub End Class Figure 7: An
IHttpHandler implementation that uses the HttpContext to determine if the user
has been authenticated. Configuring the handler factory follows a similar pattern
to the handler: "ImageHandlerFactory"/> "ImageHandlerFactory"/> From this article you can gather that there are many ways
to incorporate the various implementations of HTTP handlers and HTTP handler factories
in your Web applications. The simple Web handler is great for providing new
endpoints externally or internally when you are not overriding IIS extension
mappings. More importantly, they require much less overhead than the Page
handler and give you full control over the output. Custom IHttpHandler
components make it possible to use a handler behind the scenes without altering
file extensions, in addition to making the handler completely configurable with
the flip of a If you have questions or comments regarding this column, or
any other ASP.NET topics, please drop me a line at [email protected].
Thanks for reading! C# and VB.NET code
examples are available for download. Michele Leroux
Bustamante is Chief Architect at IDesign Inc., Microsoft Regional Director
for San Diego, Microsoft MVP for XML Web services, and a BEA Technical
Director. At IDesign Michele provides training, mentoring, and high-end
architecture consulting services, specializing in scalable and secure .NET
architecture design, globalization, Web services, and interoperability with
Java platforms. She is a board member for the International Association of
Software Architects (IASA), a frequent conference presenter, conference chair
of SD s Web Services track, and a frequently published author. She is currently
writing a book for O Reilly on the Windows Communication Foundation. Reach her
at http://www.idesign.net or http://www.dasblonde.net. IDesign: http://www.idesign.net Michele s blog: http://www.dasblonde.net Michele s WCF book: http://www.thatindigogirl.com
Figure 2: The execution of an HTTP
handler in the context of a round-trip. Quick Review of IHttpHandlerFactory and IHttpHandler
Creating a Simple Handler
Figure 3: Adding a generic handler
to a Web site.
")
Creating a Configurable Handler Component
Figure 5: Adding an extension
mapping for .jpg files. When Do I Need a Custom Handler Factory?
Conclusion
Additional Resources