Share via


Writing HTTP Handlers

ASP.NET comes with a small set of built-in HTTP handlers. There is a handler to serve ASP.NET pages, one for Web services, and yet another to accommodate .NET Remoting requests for remote objects hosted by IIS. Other helper handlers are defined to view the tracing of individual pages in a Web application (trace.axd) and to block requests for prohibited resources such as .config or .asax files. Starting with ASP.NET 2.0, you also find a handler (webresource.axd) to inject assembly resources and script code into pages. In ASP.NET 3.5, the scriptresource.axd handler has been added as a more refined tool to inject script code and AJAX capabilities into Web pages.

You can write custom HTTP handlers whenever you need ASP.NET to process certain requests in a nonstandard way. The list of useful things you can do with HTTP handlers is limited only by your imagination. Through a well-written handler, you can have your users invoke any sort of functionality via the Web. For example, you could implement click counters and any sort of image manipulation, including dynamic generation of images, server-side caching, or obstructing undesired linking to your images.

Note An HTTP handler can either work synchronously or operate in an asynchronous way. When working synchronously, a handler doesn't return until it's done with the HTTP request. An asynchronous handler, on the other hand, launches a potentially lengthy process and returns immediately after. A typical implementation of asynchronous handlers are asynchronous pages. Later in this chapter, though, we'll take a look at the mechanics of asynchronous handlers, of which asynchronous pages are a special case.

Conventional ISAPI extensions and filters should be registered within the IIS metabase. In contrast, HTTP handlers are registered in the web.config file if you want the handler to participate in the HTTP pipeline processing of the Web request. In a manner similar to ISAPI extensions, you can also invoke the handler directly via the URL.

The IHttpHandler Interface

Want to take the splash and dive into HTTP handler programming? Well, your first step is getting the hang of the IHttpHandler interface. An HTTP handler is just a managed class that implements that interface. More specifically, a synchronous HTTP handler implements the IHttpHandler interface; an asynchronous HTTP handler, on the other hand, implements the IHttpAsyncHandler interface. Let's tackle synchronous handlers first.

The contract of the IHttpHandler interface defines the actions that a handler needs to take to process an HTTP request synchronously.

Members of the IHttpHandler Interface

The IHttpHandler interface defines only two members—ProcessRequest and IsReusable, as shown in Table 18-1. ProcessRequest is a method, whereas IsReusable is a Boolean property.

Table 18-1 Members of the IHttpHandler Interface

Dd163840.table_C18625273_1(en-us,MSDN.10).png

The IsReusable property on the System.Web.UI.Page class—the most common HTTP handler in ASP.NET—returns false, meaning that a new instance of the HTTP request is needed to serve each new page request. You typically make IsReusable return false in all situations where some significant processing is required that depends on the request payload. Handlers used as simple barriers to filter special requests can set IsReusable to true to save some CPU cycles. I'll return to this subject with a concrete example in a moment.

The ProcessRequest method has the following signature:

void ProcessRequest(HttpContext context);

It takes the context of the request as the input and ensures that the request is serviced. In the case of synchronous handlers, when ProcessRequest returns, the output is ready for forwarding to the client.

A Very Simple HTTP Handler

Again, an HTTP handler is simply a class that implements the IHttpHandler interface. The output for the request is built within the ProcessRequest method, as shown in the following code:

using System.Web;

namespace Core35.Components
{
    public class SimpleHandler : IHttpHandler 
    {
        // Override the ProcessRequest method
        public void ProcessRequest(HttpContext context) 
        {
            context.Response.Write("<H1>Hello, I'm an HTTP handler</H1>");
        }

        // Override the IsReusable property
        public bool IsReusable 
        {
            get { return true; }
        }
    }
}

You need an entry point to be able to call the handler. In this context, an entry point into the handler's code is nothing more than an HTTP endpoint—that is, a public URL. The URL must be a unique name that IIS and the ASP.NET runtime can map to this code. When registered, the mapping between an HTTP handler and a Web server resource is established through the web.config file:

<configuration>
    <system.web>
        <httpHandlers>
            <add verb="*" path="hello.aspx" 
                type="Core35.Components.SimpleHandler" />
        </httpHandlers>
    </system.web>
</configuration>

The <httpHandlers> section lists the handlers available for the current application. These settings indicate that SimpleHandler is in charge of handling any incoming requests for an endpoint named hello.aspx. Note that the URL hello.aspx doesn't have to be a physical resource on the server; it's simply a public resource identifier. The type attribute references the class and assembly that contains the handler. It's canonical format is type[,assembly]. You omit the assembly information if the component is defined in the App_Code or other reserved folders.

Note If you enter the settings shown earlier in the global web.config file, you will register the SimpleHandler component as callable from within all Web applications hosted by the server machine.

If you invoke the hello.aspx URL, you obtain the results shown in Figure 18-2.

Dd163840.figure_C18625273_2(en-us,MSDN.10).png

Figure 18-2 A sample HTTP handler that answers requests for hello.aspx.

The technique discussed here is the quickest and simplest way of putting an HTTP handler to work, but there is more to know about registration of HTTP handlers and there are many more options to take advantage of. Now let's consider a more complex example of an HTTP handler.

An HTTP Handler for Quick Data Reports

With their relatively simple programming model, HTTP handlers give you a means of interacting with the low-level request and response services of IIS. In the previous example, we returned only constant text and made no use of the request information. In the next example, we'll configure the handler to intercept and process only requests of a particular type and generate the output based on the contents of the requested resource.

The idea is to build an HTTP handler for custom .sqlx resources. A SQLX file is an XML document that expresses the statements for one or more SQL queries. The handler grabs the information about the query, executes it, and finally returns the result set formatted as a grid. Figure 18-3 shows the expected outcome.

Dd163840.figure_C18625273_3(en-us,MSDN.10).png

Figure 18-3 A custom HTTP handler in action.

To start, let's examine the source code for the IHttpHandler class.

Warning Take this example for what it really is—merely a way to process a custom XML file with a custom extension doing something more significant than outputting a "hello world" message. Do not take this handler as a realistic prototype for exposing your Microsoft SQL Server databases over the Web.

Building a Query Manager Tool

The HTTP handler should get into the game whenever the user requests an .sqlx resource. Assume for now that the system knows how to deal with such a weird extension, and focus on what's needed to execute the query and pack the results into a grid. To execute the query, at a minimum, we need the connection string and the command text. The following text illustrates the typical contents of an .sqlx file:

<queries>
  <query connString="DATABASE=northwind;SERVER=localhost;UID...;">
    SELECT firstname, lastname, country FROM employees
  </query>
  <query connString="DATABASE=northwind;SERVER=localhost;UID=...;">
    SELECT companyname FROM customers WHERE country='Italy'
  </query>
</queries>

The XML document is formed by a collection of <query> nodes, each containing an attribute for the connection string and the text of the query.

The ProcessRequest method extracts this information before it can proceed with executing the query and generating the output:

class SqlxData
{
    public string ConnectionString;
    public string QueryText;
}

public class QueryHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        // Parses the SQLX file 
        SqlxData[] data = ParseFile(context);

        // Create the output as HTML
        StringCollection htmlColl = CreateOutput(data);

        // Output the data
        context.Response.Write("<html><head><title>");
        context.Response.Write("QueryHandler Output");
        context.Response.Write("</title></head><body>");
        foreach (string html in htmlColl)
        {
            context.Response.Write(html);
            context.Response.Write("<hr />");
        }
        context.Response.Write("</body></html>");
    }

    // Override the IsReusable property
    public bool IsReusable
    {
        get { return true; }
    }

    ...
}

The ParseFile helper function parses the source code of the .sqlx file and creates an instance of the SqlxData class for each query found:

private SqlxData[] ParseFile(HttpContext context)
{
   XmlDocument doc = new XmlDocument();
   string filePath = context.Request.Path;
   using (Stream fileStream = VirtualPathProvider.OpenFile(filePath))  {
       doc.Load(fileStream);
   }

   // Visit the <mapping> nodes
   XmlNodeList mappings = doc.SelectNodes("queries/query");
   SqlxData[] descriptors = new SqlxData[mappings.Count];
   for (int i=0; i < descriptors.Length; i++)
   {
       XmlNode mapping = mappings[i];
       SqlxData query = new SqlxData();
       descriptors[i] = query;

       try {
           query.ConnectionString =
              mapping.Attributes["connString"].Value;
           query.QueryText = mapping.InnerText;
       }
       catch {
           context.Response.Write("Error parsing the input file.");
           descriptors = new SqlxData[0]; 
           break;
       }
   }
   return descriptors;
}

The SqlxData internal class groups the connection string and the command text. The information is passed to the CreateOutput function, which will actually execute the query and generate the grid:

private StringCollection CreateOutput(SqlxData[] descriptors)
{
    StringCollection coll = new StringCollection();

    foreach (SqlxData data in descriptors)
    {
        // Run the query
        DataTable dt = new DataTable();
        SqlDataAdapter adapter = new SqlDataAdapter(data.QueryText, 
            data.ConnectionString);
        adapter.Fill(dt);

        // Error handling
        ...
        // Prepare the grid
        DataGrid grid = new DataGrid();
        grid.DataSource = dt;
        grid.DataBind();

        // Get the HTML
        string html = Utils.RenderControlAsString(grid);
        coll.Add(html); 
    }
    return coll;
}

After executing the query, the method populates a dynamically created DataGrid control. In ASP.NET pages, the DataGrid control, like any other control, is rendered to HTML. However, this happens through the care of the special HTTP handler that manages .aspx resources. For .sqlx resources, we need to provide that functionality ourselves. Obtaining the HTML for a Web control is as easy as calling the RenderControl method on an HTML text writer object. This is just what the helper method RenderControlAsString does:

static class Utils
{
    public static string RenderControlAsString(Control ctl)
    {
        StringWriter sw = new StringWriter();
        HtmlTextWriter writer = new HtmlTextWriter(sw);
        ctl.RenderControl(writer);
        return sw.ToString();
    }
}

Note An HTTP handler that needs to access session-state values must implement the IRequiresSessionState interface. Like INamingContainer, it's a marker interface and requires no method implementation. Note that the IRequiresSessionState interface indicates that the HTTP handler requires read and write access to the session state. If read-only access is needed, use the IReadOnlySessionState interface instead.

Registering the Handler

An HTTP handler is a class and must be compiled to an assembly before you can use it. The assembly must be deployed to the Bin directory of the application. If you plan to make this handler available to all applications, you can copy it to the global assembly cache (GAC). The next step is registering the handler with an individual application or with all the applications running on the Web server. You register the handler in the configuration file:

<system.web>
  <httpHandlers>
    <add verb="*" 
      path="*.sqlx" 
      type= "Core35.Components.QueryHandler,Core35Lib" />
  </httpHandlers>
</system.web>

You add the new handler to the <httpHandlers> section of the local or global web.config file. The section supports three actions: <add>, <remove>, and <clear>. You use <add> to add a new HTTP handler to the scope of the .config file. You use <remove> to remove a particular handler. Finally, you use <clear> to get rid of all the registered handlers. To add a new handler, you need to set three attributes—verb, path, and type—as shown in Table 18-2.

Table 18-2 Attributes Needed to Register an HTTP Handler

Dd163840.table_C18625273_2(en-us,MSDN.10).png

These attributes are mandatory. An optional attribute is also supported—validate. When validate is set to false, ASP.NET delays as much as possible loading the assembly with the HTTP handler. In other words, the assembly will be loaded only when a request for it arrives. ASP.NET will not try to preload the assembly, thus catching any error or problem with it.

So far, you have correctly deployed and registered the HTTP handler, but if you try invoking an .sqlx resource, the results you produce are not what you'd expect. The problem lies in the fact that so far you configured ASP.NET to handle only .sqlx resources, but IIS still doesn't know anything about them!

A request for an .sqlx resource is handled by IIS before it is handed to the ASP.NET ISAPI extension. If you don't register some ISAPI extension to handle ..sqlx resource requests, IIS will treat each request as a request for a static resource and serve the request by sending back the source code of the .sqlx file. The extra step required is registering the .sqlx extension with the IIS 6.0 metabase such that requests for .sqlx resources are handed off to ASP.NET, as shown in Figure 18-4.

Dd163840.figure_C18625273_4(en-us,MSDN.10).png

Figure 18-4 Registering the .sqlx extension with the IIS 6.0 metabase.

The dialog box in the figure is obtained by clicking on the properties of the application in the IIS 6.0 manager and then the configuration of the site. To involve the HTTP handler, you must choose aspnet_isapi.dll as the ISAPI extension. In this way, all .sqlx requests are handed out to ASP.NET and processed through the specified handler. Make sure you select aspnet_isapi.dll from the folder of the ASP.NET version you plan to use.

Caution In Microsoft Visual Studio, if you test a sample .sqlx resource using the local embedded Web server, nothing happens that forces you to register the .sqlx resource with IIS. This is just the point, though. You're not using IIS! In other words, if you use the local Web server, you have no need to touch IIS; you do need to register any custom resource you plan to use with IIS before you get to production.

Registering the Handler with IIS 7.0

If you run IIS 7.0, you don't strictly need to change anything through the IIS Manager. You can add a new section to the web.config file and specify the HTTP handler also for static resources that would otherwise be served directly by IIS. Here's what you need to enter:

<system.webServer>
    <add verb="*" 
        path="*.sqlx" 
        type="Core35.Components.QueryHandler, Core35Lib" />
</system.webServer>

The new section is a direct child of the root tag <configuration>. Without this setting, IIS can't recognize the page and won't serve it up. The configuration script instructs IIS 7.0 to forward any *.sqlx requests to your application, which knows how to deal with it.

The Picture Viewer Handler

Let's examine another scenario that involves custom HTTP handlers. Thus far, we have explored custom resources and realized how important it is to register any custom extensions with IIS.

To speed up processing, IIS claims the right of personally serving some resources that typically form a Web application without going down to a particular ISAPI extension. The list includes static files such as images and HTML files. What if you request a GIF or a JPG file directly from the address bar of the browser? IIS retrieves the specified resource, sets the proper content type on the response buffer, and writes out the bytes of the file. As a result, you'll see the image in the browser's page. So far so good.

What if you point your browser to a virtual folder that contains images? In this case, IIS doesn't distinguish the contents of the folder and returns a list of files, as shown in Figure 18-5.

Dd163840.figure_C18625273_5(en-us,MSDN.10).png

Figure 18-5 The standard IIS-provided view of a folder.

Wouldn't it be nice if you could get a preview of the contained pictures, instead?

Designing the HTTP Handler

To start out, you need to decide how you would let IIS know about your wishes. You can use a particular endpoint that, appended to a folder's name, convinces IIS to yield to ASP.NET and provide a preview of contained images. Put another way, the idea is binding our picture viewer handler to a particular endpoint—say, folder.axd. As mentioned earlier in the chapter, a fixed endpoint for handlers doesn't have to be an existing, deployed resource. You make the folder.axd endpoint follow the folder name, as shown here:

https://www.contoso.com/images/folder.axd

The handler will process the URL, extract the folder name, and select all the contained pictures.

Note In ASP.NET, the .axd extension is commonly used for endpoints referencing a special service. Trace.axd for tracing and WebResource.axd for script and resources injection are examples of two popular uses of the extension. In particular, the Trace.axd handler implements the same logic described here. If you append its name to the URL, it will trace all requests for pages in that application.

Implementing the HTTP Handler

The picture viewer handler returns a page composed of a multirow table showing as many images as there are in the folder. Here's the skeleton of the class:

class PictureViewerInfo
{
    public PictureViewerInfo() {
        DisplayWidth = 200;
        ColumnCount = 3;
    }
    public int DisplayWidth;
    public int ColumnCount;
    public string FolderName;
} 

public class PictureViewerHandler : IHttpHandler
{
    // Override the ProcessRequest method
    public void ProcessRequest(HttpContext context)
    {
        PictureViewerInfo info = GetFolderInfo(context);
        string html = CreateOutput(info);

        // Output the data
        context.Response.Write("<html><head><title>");
        context.Response.Write("Picture Web Viewer");
        context.Response.Write("</title></head><body>");
        context.Response.Write(html);
        context.Response.Write("</body></html>");
    }

    // Override the IsReusable property
    public bool IsReusable
    {
        get { return true; }
    }
    ...
}

Retrieving the actual path of the folder is as easy as stripping off the folder.axd string from the URL and trimming any trailing slashes or backslashes. Next, the URL of the folder is mapped to a server path and processed using the .NET Framework API for files and folders:

private ArrayList GetAllImages(string path)
{
    string[] fileTypes = { "*.bmp", "*.gif", "*.jpg", "*.png" };
    ArrayList images = new ArrayList();
    DirectoryInfo di = new DirectoryInfo(path);
    foreach (string ext in fileTypes)
    {
        FileInfo[] files = di.GetFiles(ext);
        if (files.Length > 0)
            images.AddRange(files);
    }
    return images;
   }

The DirectoryInfo class provides some helper functions on the specified directory; for example, the GetFiles method selects all the files that match the given pattern. Each file is wrapped by a FileInfo object. The method GetFiles doesn't support multiple search patterns; to search for various file types, you need to iterate for each type and accumulate results in an array list or equivalent data structure.

After you get all the images in the folder, you move on to building the output for the request. The output is a table with a fixed number of cells and a variable number of rows to accommodate all selected images. The image is not downloaded as a thumbnail, but it is more simply rendered in a smaller area. For each image file, a new <img> tag is created through the Image control. The width attribute of this file is set to a fixed value (say, 200 pixels), causing most modern browsers to automatically resize the image. Furthermore, the image is wrapped by an anchor that links to the same image URL. As a result, when the user clicks on an image, the page refreshes and shows the same image at its natural size.

string CreateOutputForFolder(PictureViewerInfo info) 
{
    ArrayList images = GetAllImages(info.FolderName);
    Table t = new Table();

    int index = 0;
    bool moreImages = true;
    while (moreImages)
    {
        TableRow row = new TableRow();
        t.Rows.Add(row);
        for (int i = 0; i < info.ColumnCount; i++)
        {
            TableCell cell = new TableCell();
            row.Cells.Add(cell);

            // Create the image
            Image img = new Image();
            FileInfo fi = (FileInfo)images[index];
            img.ImageUrl = fi.Name;
            img.Width = Unit.Pixel(info.DisplayWidth);

            // Wrap the image in an anchor so that a larger image 
            // is shown when the user clicks
            HtmlAnchor a = new HtmlAnchor();
            a.HRef = fi.Name;
            a.Controls.Add(img);
            cell.Controls.Add(a);

            // Check whether there are more images to show
            index++;
            moreImages = (index < images.Count);
            if (!moreImages)
                break;
        }
    }           
}

You might want to make the handler accept some optional query string parameters, such as width and column count. These values are packed in an instance of the helper class PictureViewerInfo along with the name of the folder to view. Here's the code to process the query string of the URL to extract parameters if any are present:

PictureViewerInfo info = new PictureViewerInfo();
object p1 = context.Request.Params["Width"];
object p2 = context.Request.Params["Cols"];
if (p1 != null)
    Int32.TryParse((string)p1, out info.DisplayWidth);
if (p2 != null)
    Int32.TryParse((string)p2, out info.ColumnCount);

Figure 18-6 shows the handler in action.

Dd163840.figure_C18625273_6(en-us,MSDN.10).png

Figure 18-6 The picture viewer handler in action with a given number of columns and width.

Registering the handler is easy too. You just add the following script to the web.config file:

<add verb="*" path="folder.axd" 
    type="Core35.Components.PictureViewerHandler,Core35Lib" />

You place the assembly in the GAC and move the configuration script to the global web.config to extend the settings to all applications on the machine.

Serving Images More Effectively

Any page we get from the Web today is topped with so many images and is so well conceived and designed that often the overall page looks more like a magazine advertisement than an HTML page. Looking at the current pages displayed by portals, it's rather hard to imagine there ever was a time—and it was only seven or eight years ago—when one could create a Web site by using only a text editor and some assistance from a friend who had a bit of familiarity with Adobe PhotoShop.

In spite of the wide use of images on the Web, there is just one way in which a Web page can reference an image—by using the HTML <img> tag. By design, this tag points to a URL. As a result, to be displayable within a Web page, an image must be identifiable through a URL and its bits should be contained in the output stream returned by the Web server for that URL.

In many cases, the URL points to a static resource such as a GIF or JPEG file. In this case, the Web server takes the request upon itself and serves it without invoking external components. However, the fact that many <img> tags on the Web are bound to a static file does not mean there's no other way to include images in Web pages.

Where else can you turn to get images aside from picking them up from the server file system? For example, you can load images from a database or you can generate or modify them on the fly just before serving the bits to the browser.

Loading Images from Databases

The use of a database as the storage medium for images is controversial. Some people have good reasons to push it as a solution; others tell you bluntly they would never do it and that you shouldn't either. Some people can tell you wonderful stories of how storing images in a properly equipped database was the best experience of their professional life. With no fear that facts could perhaps prove them wrong, other people will confess that they would never use a database again for such a task.

The facts say that all database management systems (DBMS) of a certain reputation and volume have supported binary large objects (BLOB) for quite some time. Sure, a BLOB field doesn't necessarily contain an image—it can contain a multimedia file or a long text file—but overall there must be a good reason for having this BLOB support in SQL Server, Oracle, and similar popular DBMS systems!

To read an image from a BLOB field with ADO.NET, you execute a SELECT statement on the column and use the ExecuteScalar method to catch the result and save it in an array of bytes. Next, you send this array down to the client through a binary write to the response stream. Let's write an HTTP handler to serve a database-stored image:

public class DbImageHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext ctx)
    {
        // Ensure the URL contains an ID argument that is a number
        int id = -1;
        bool result = Int32.TryParse(ctx.Request.QueryString["id"], out id);
        if (!result)
           ctx.Response.End();

        string connString = "...";
        string cmdText = "SELECT photo FROM employees WHERE employeeid=@id";

        // Get an array of bytes from the BLOB field
        byte[] img = null;
        SqlConnection conn = new SqlConnection(connString);
        using (conn)
        {
            SqlCommand cmd = new SqlCommand(cmdText, conn);
            cmd.Parameters.AddWithValue("@id", id);
            conn.Open();
            img = (byte[])cmd.ExecuteScalar();
            conn.Close();
       }

        // Prepare the response for the browser
        if (img != null)
        {
            ctx.Response.ContentType = "image/jpeg";
            ctx.Response.BinaryWrite(img);
        }
    }

    public bool IsReusable
    {
        get { return true; }
    }
}

There are quite a few assumptions made in this code. First, we assume that the field named photo contains image bits and that the format of the image is JPEG. Second, we assume that images are to be retrieved from a fixed table of a given database through a predefined connection string. Finally, we're assuming that the URL to invoke this handler includes a query string parameter named id.

Notice the attempt to convert the value of the id query parameter to an integer before proceeding. This simple check significantly reduces the surface attack for malicious users by verifying that what is going to be used as a numeric ID is really a numeric ID. Especially when you're inoculating user input into SQL query commands, filtering out extra characters and wrong data types is a fundamental measure for preventing attacks.

The BinaryWrite method of the HttpResponse object writes an array of bytes to the output stream.

Warning If the database you're using is Northwind (as in the preceding example), an extra step might be required to ensure that the images are correctly managed. For some reason, the SQL Server version of the Northwind database stores the images in the photo column of the Employees table as OLE objects. This is probably because of the conversion that occurred when the database was upgraded from the Microsoft Access version. As a matter fact, the array of bytes you receive contains a 78-byte prefix that has nothing to do with the image. Those bytes are just the header created when the image was added as an OLE object to the first version of Access. Although the preceding code works like a champ with regular BLOB fields, it must undergo the following modification to work with the photo field of the Northwind.Employees database:

Response.OutputStream.Write(img, 78, img.Length);

Instead of using the BinaryWrite call, which doesn't let you specify the starting position, use the code shown here.

A sample page to test BLOB field access is shown in Figure 18-7. The page lets users select an employee ID and post back. When the page renders, the ID is used to complete the URL for the ASP.NET Image control.

string url = String.Format("dbimage.axd?id={0}", 
                           DropDownList1.SelectedValue);
Image1.ImageUrl = url;

Dd163840.figure_C18625273_7(en-us,MSDN.10).png

Figure 18-7 Downloading images stored within the BLOB field of a database.

An HTTP handler must be registered in the web.config file and bound to a public endpoint. In this case, the endpoint is dbimage.axd and the script to enter in the configuration file is shown next:

<httpHandlers>
    <add verb="*" path="dbimage.axd"
         type="Core35.Components.DbImageHandler,Core35Lib"/>
</httpHandlers>

Note The preceding handler clearly has a weak point: it hard-codes a SQL command and the related connection string. This means that you might need a different handler for each different command or database to access. A more realistic handler would probably use an external and configurable database-specific provider. Such a provider can be as simple as a class that implements an agreed interface. At a minimum, the interface will supply a method to retrieve and return an array of bytes. Alternatively, if you want to keep the ADO.NET code in the handler itself, the interface will just supply members that specify the command text and connection string. The handler will figure out its default provider from a given entry in the web.config file.

Serving Dynamically Generated Images

Isn't it true that an image is worth thousands of words? Many financial Web sites offer charts and, more often than not, these charts are dynamically generated on the server. Next, they are served to the browser as a stream of bytes and travel over the classic response output stream. But can you create and manipulate server-side images? For these tasks, Web applications normally rely on ad hoc libraries or the graphic engine of other applications (for example, Microsoft Office applications).

ASP.NET applications are different and, to some extent, luckier. ASP.NET applications, in fact, can rely on a powerful and integrated graphic engine capable of providing an object model for image generation. This server-side system is GDI+, and contrary to what some people might have you believe, GDI+ is fair game for generating images on the fly for ASP.NET applications.

As its name suggests, GDI+ is the successor of GDI, the Graphics Device Interface included with versions of the Windows operating system that shipped before Windows XP. The .NET Framework encapsulates the key GDI+ functionalities in a handful of managed classes and makes those functions available to Web, Windows Forms, and Web service applications.

Most of the GDI+ services belong to the following categories: 2D vector graphics and imaging. 2D vector graphics involve drawing simple figures such as lines, curves, and polygons. Under the umbrella of imaging are functions to display, manipulate, save, and convert bitmap and vector images. Finally, a third category of functions can be identified—typography, which includes the display of text in a variety of fonts, sizes, and styles. Having the goal of creating images dynamically, we are most interested in drawing figures and text and in saving the work as JPEGs or GIFs.

In ASP.NET, writing images to disk might require some security adjustments. Normally, the ASP.NET runtime runs under the aegis of the NETWORK SERVICE user account. In the case of anonymous access with impersonation disabled—which are the default settings in ASP.NET—the worker process lends its own identity and security token to the thread that executes the user request of creating the file. With regard to the default scenario, an access denied exception might be thrown if NETWORK SERVICE lacks writing permissions on virtual directories—a pretty common situation.

ASP.NET and GDI+ provide an interesting alternative to writing files on disk without changing security settings: in-memory generation of images. In other words, the dynamically generated image is saved directly to the output stream in the needed image format or in a memory stream.

GDI+ supports quite a few image formats, including JPEG, GIF, BMP, and PNG. The whole collection of image formats is in the ImageFormat structure from the System.Drawing namespace. You can save a memory-resident Bitmap object to any of the supported formats by using one of the overloads of the Save method:

Bitmap bmp = new Bitmap(file);
...
bmp.Save(outputStream, ImageFormat.Gif);

When you attempt to save an image to a stream or disk file, the system attempts to locate an encoder for the requested format. The encoder is a GDI+ module that converts from the native format to the specified format. Note that the encoder is a piece of unmanaged code that lives in the underlying Win32 platform. For each save format, the Save method looks up the right encoder and proceeds.

The next example wraps up all the points we touched on. This example shows how to load an existing image, add some copyright notes, and serve the modified version to the user. In doing so, we'll load an image into a Bitmap object, obtain a Graphics for that bitmap, and use graphics primitives to write. When finished, we'll save the result to the page's output stream and indicate a particular MIME type.

The sample page that triggers the example is easily created, as shown in the following listing:

<html>
<body>
    <img id="picture" src="dynimage.axd?url=images/pic1.jpg" />
</body>
</html>

The page contains no ASP.NET code and displays an image through a static HTML <img> tag. The source of the image, though, is an HTTP handler that loads the image passed through the query string, and then manipulates and displays it. Here's the source code for the ProcessRequest method of the HTTP handler:

public void ProcessRequest (HttpContext context) 
{
    object o = context.Request["url"];
    if (o == null)
    {
        context.Response.Write("No image found.");
        context.Response.End();
        return;
    }

    string file = context.Server.MapPath((string)o);
    string msg = ConfigurationManager.AppSettings["CopyrightNote"];
    if (File.Exists(file))
    {
        Bitmap bmp = AddCopyright(file, msg);
        context.Response.ContentType = "image/jpeg";
        bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
        bmp.Dispose();
    }
    else
    {
        context.Response.Write("No image found.");
        context.Response.End();
    }
}

Note that the server-side page performs two different tasks indeed. First, it writes copyright text on the image canvas; next, it converts whatever the original format was to JPEG:

Bitmap AddCopyright(string file, string msg) 
{
    // Load the file and create the graphics
    Bitmap bmp = new Bitmap(file);
    Graphics g = Graphics.FromImage(bmp);

    // Define text alignment
    StringFormat strFmt = new StringFormat();
    strFmt.Alignment = StringAlignment.Center;

    // Create brushes for the bottom writing
    // (green text on black background)
    SolidBrush btmForeColor = new SolidBrush(Color.PaleGreen);
    SolidBrush btmBackColor = new SolidBrush(Color.Black);

    // To calculate writing coordinates, obtain the size of the 
    // text given the font typeface and size
    Font btmFont = new Font("Verdana", 7);
    SizeF textSize = new SizeF();
    textSize = g.MeasureString(msg, btmFont);

    // Calculate the output rectangle and fill
    float x = ((float) bmp.Width-textSize.Width-3);
    float y = ((float) bmp.Height-textSize.Height-3);
    float w = ((float) x + textSize.Width);
    float h = ((float) y + textSize.Height);
    RectangleF textArea = new RectangleF(x, y, w, h);
    g.FillRectangle(btmBackColor, textArea);

    // Draw the text and free resources
    g.DrawString(msg, btmFont, btmForeColor, textArea);
    btmForeColor.Dispose();
    btmBackColor.Dispose();
    btmFont.Dispose();
    g.Dispose();

    return bmp;
}

Figure 18-8 shows the results.

Note that the additional text is part of the image the user downloads on her client browser. If the user saves the picture by using the Save Picture As menu from the browser, the text (in this case, the copyright note) is saved along with the image.

Dd163840.figure_C18625273_8(en-us,MSDN.10).png

Figure 18-8 A server-resident image has been modified before being displayed.

Note What if the user requests the JPG file directly from the address bar? And what if the image is linked by another Web site or referenced in a blog post? In these cases, the original image is served without any further modification. Why is it so? As mentioned, for performance reasons IIS serves static files, such as JPG images, directly without involving any external module, including the ASP.NET runtime. The HTTP handler that does the trick of adding a copyright note is therefore blissfully ignored when the request is made via the address bar or a hyperlink. What can you do about it?

In IIS 6.0, you must register the JPG extension as an ASP.NET extension for a particular application using the IIS Manager as shown in Figure 18-4. In this case, each request for JPG resources is forwarded to your application and resolved through the HTTP handler.

In IIS 7.0, things are even simpler for developers. All that you have to do is add the following lines to the application's web.config file:

<system.webServer>
    <handlers>
        <add verb="*" 
            path="*.jpg" 
            type="Core35.Components.DynImageHandler,Core35Lib" />
    </handlers>
</system.webServer>

The system.webServer section is a direct child of the root configuration node.

Advanced HTTP Handler Programming

HTTP handlers are not a tool for everybody. They serve a very neat purpose: changing the way a particular resource, or set of resources, is served to the user. You can use handlers to filter out resources based on runtime conditions or to apply any form of additional logic to the retrieval of traditional resources such as pages and images. Finally, you can use HTTP handlers to serve certain pages or resources in an asynchronous manner.

For HTTP handlers, the registration step is key. Registration enables ASP.NET to know about your handler and its purpose. Registration is required for two practical reasons. First, it serves to ensure that IIS forwards the call to the correct ASP.NET application. Second, it serves to instruct your ASP.NET application on the class to load to "handle" the request. As mentioned, you can use handlers to override the processing of existing resources (for example, hello.aspx) or to introduce new functionalities (for example, folder.axd). In both cases, you're invoking a resource whose extension is already known to IIS—the .axd extension is registered in the IIS metabase when you install ASP.NET. In both cases, though, you need to modify the web.config file of the application to let the application know about the handler.

By using the ASHX extension and programming model for handlers, you can also save yourself the web.config update and deploy a new HTTP handler by simply copying a new file in a new or existing application's folder.

Deploying Handlers as ASHX Resources

An alternative way to define an HTTP handler is through an .ashx file. The file contains a special directive, named @WebHandler, that expresses the association between the HTTP handler endpoint and the class used to implement the functionality. All .ashx files must begin with a directive like the following one:

<%@ WebHandler Language="C#" Class="Core35.Components.YourHandler" %>

When an .ashx endpoint is invoked, ASP.NET parses the source code of the file and figures out the HTTP handler class to use from the @WebHandler directive. This automation removes the need of updating the web.config file. Here's a sample .ashx file. As you can see, it is the plain class file plus the special @WebHandler directive:

<%@ WebHandler Language="C#" Class="MyHandler" %>

using System.Web;

public class MyHandler : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World");
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}

Note that the source code of the class can either be specified inline or loaded from any of the assemblies referenced by the application. When .ashx resources are used to implement an HTTP handler, you just deploy the source file, and you're done. Just as for XML Web services, the source file is loaded and compiled only on demand. Because ASP.NET adds a special entry to the IIS metabase for .ashx resources, you don't even need to enter changes to the Web server configuration.

Resources with an .ashx extension are handled by an HTTP handler class named SimpleHandleFactory. Note that SimpleHandleFactory is actually an HTTP handler factory class, not a simple HTTP handler class. We'll discuss handler factories in a moment.

The SimpleHandleFactory class looks for the @WebHandler directive at the beginning of the file. The @WebHandler directive tells the handler factory the name of the HTTP handler class to instantiate once the source code has been compiled.

Important You can build HTTP handlers both as regular class files compiled to an assembly and via .ashx resources. There's no significant difference between the two approaches except that .ashx resources, like ordinary ASP.NET pages, will be compiled on the fly upon the first request.

Prevent Access to Forbidden Resources

If your Web application manages resources of a type that you don't want to make publicly available over the Web, you must instruct IIS not to display those files. A possible way to accomplish this consists of forwarding the request to aspnet_isapi and then binding the extension to one of the built-in handlers—the HttpForbiddenHandler class:

<add verb="*" path="*.xyz" type="System.Web.HttpForbiddenHandler" />

Any attempt to access an .xyz resource results in an error message being displayed. The same trick can also be applied for individual resources served by your application. If you need to deploy, say, a text file but do not want to take the risk that somebody can get to them, add the following:

<add verb="*" path="yourFile.txt" type="System.Web.HttpForbiddenHandler" />

Should It Be Reusable or Not?

In a conventional HTTP handler, the ProcessRequest method takes the lion's share of the overall set of functionality. The second member of the IHttpHandler interface—the IsReusable property—is used only in particular circumstances. If you set the IsReusable property to return true, the handler is not unloaded from memory after use and is repeatedly used. Put another way, the Boolean value returned by IsReusable indicates whether the handler object can be pooled.

Frankly, most of the time it doesn't really matter what you return—be it true or false. If you set the property to return false, you require that a new object be allocated for each request. The simple allocation of an object is not a particularly expensive operation. However, the initialization of the handler might be costly. In this case, by making the handler reusable, you save much of the overhead. If the handler doesn't hold any state, there's no reason for not making it reusable.

In summary, I'd say that IsReusable should be always set to true, except when you have instance properties to deal with or properties that might cause trouble if used in a concurrent environment. If you have no initialization tasks, it doesn't really matter whether it returns true or false. As a margin note, the System.Web.UI.Page class—the most popular HTTP handler ever—sets its IsReusable property to false.

The key point to make is the following. Who's really using IsReusable and, subsequently, who really cares about its value?

Once the HTTP runtime knows the HTTP handler class to serve a given request, it simply instantiates it—no matter what. So when is the IsReusable property of a given handler taken into account? Only if you use an HTTP handler factory—that is, a piece of code that dynamically decides which handler should be used for a given request. An HTTP handler factory can query a handler to determine whether the same instance can be used to service multiple requests and thus optionally create and maintain a pool of handlers.

ASP.NET pages and ASHX resources are served through factories. However, none of these factories ever checks IsReusable. Of all the built-in handler factories in the whole ASP.NET platform, very few check the IsReusable property of related handlers. So what's the bottom line?

As long as you're creating HTTP handlers for AXD, ASHX, or perhaps ASPX resources, be aware that the IsReusable property is blissfully ignored. Do not waste your time trying to figure out the optimal configuration. Instead, if you're creating an HTTP handler factory to serve a set of resources, whether or not to implement a pool of handlers is up to you and IsReusable is the perfect tool for the job.

But when should you employ an HTTP handler factory? In all situations in which the HTTP handler class for a request is not uniquely identified. For example, for ASPX pages, you don't know in advance which HTTP handler type you have to use. The type might not even exist (in which case, you compile it on the fly). The HTTP handler factory is used whenever you need to apply some logic to decide which is the right handler to use. In other words, you need an HTTP handler factory when declarative binding between endpoints and classes is not enough.

HTTP Handler Factories

An HTTP request can be directly associated with an HTTP handler or with an HTTP handler factory object. An HTTP handler factory is a class that implements the IHttpHandlerFactory interface and is in charge of returning the actual HTTP handler to use to serve the request. The SimpleHandlerFactory class provides a good example of how a factory works. The factory is mapped to requests directed at .ashx resources. When such a request comes in, the factory determines the actual handler to use by looking at the @WebHandler directive in the source file.

In the .NET Framework, HTTP handler factories are used to perform some preliminary tasks on the requested resource prior to passing it on to the handler. Another good example of a handler factory object is represented by an internal class named PageHandlerFactory, which is in charge of serving .aspx pages. In this case, the factory handler figures out the name of the handler to use and, if possible, loads it up from an existing assembly.

HTTP handler factories are classes that implement a couple of methods on the IHttpHandlerFactory interface—GetHandler and ReleaseHandler, as shown in Table 18-3.

Table 18-3 Members of the IHttpHandlerFactory Interface

Dd163840.table_C18625273_3(en-us,MSDN.10).png

The GetHandler method has the following signature:

public virtual IHttpHandler GetHandler(HttpContext context, 
    string requestType, string url, string pathTranslated);

The requestType argument is a string that evaluates to GET or POST—the HTTP verb of the request. The last two arguments represent the raw URL of the request and the physical path behind it. The ReleaseHandler method is a mandatory override for any class that implements IHttpHandlerFactory; in most cases, it will just have an empty body.

The following listing shows a sample HTTP handler factory that returns different handlers based on the HTTP verb (GET or POST) used for the request:

class MyHandlerFactory : IHttpHandlerFactory
{
    public IHttpHandler GetHandler(HttpContext context, 
        string requestType, String url, String pathTranslated)
    {
        // Feel free to create a pool of HTTP handlers here
        if(context.Request.RequestType.ToLower() == "get") 
            return (IHttpHandler) new MyGetHandler();
        else if(context.Request.RequestType.ToLower() == "post")
            return (IHttpHandler) new MyPostHandler();
        return null;
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
        // Nothing to do
    }
}

When you use an HTTP handler factory, it's the factory, not the handler, that needs to be registered with the ASP.NET configuration file. If you register the handler, it will always be used to serve requests. If you opt for a factory, you have a chance to decide dynamically and based on runtime conditions which handler is more appropriate for a certain request. In doing so, you can use the IsReusable property of handlers to implement a pool.

Asynchronous Handlers

An asynchronous HTTP handler is a class that implements the IHttpAsyncHandler interface. The system initiates the call by invoking the BeginProcessRequest method. Next, when the method ends, a callback function is automatically invoked to terminate the call. In the .NET Framework, the sole HttpApplication class implements the asynchronous interface. The members of IHttpAsyncHandler interface are shown in Table 18-4.

Table 18-4 Members of the IHttpAsyncHandler Interface

Dd163840.table_C18625273_4(en-us,MSDN.10).png

The signature of the BeginProcessRequest method is as follows:

IAsyncResult BeginProcessRequest(HttpContext context, 
    AsyncCallback cb, object extraData);

The context argument provides references to intrinsic server objects used to service HTTP requests. The second parameter is the AsyncCallback object to invoke when the asynchronous method call is complete. The third parameter is a generic cargo variable that contains any data you might want to pass to the handler.

Note An AsyncCallback object is a delegate that defines the logic needed to finish processing the asynchronous operation. A delegate is a class that holds a reference to a method. A delegate class has a fixed signature, and it can hold references only to methods that match that signature. A delegate is equivalent to a type-safe function pointer or a callback. As a result, an AsyncCallback object is just the code that executes when the asynchronous handler has completed its job.

The AsyncCallback delegate has the following signature:

public delegate void AsyncCallback(IAsyncResult ar);

It uses the IAsyncResult interface to obtain the status of the asynchronous operation. To illustrate the plumbing of asynchronous handlers, I'll show you the pseudocode that the HTTP runtime employs when it deals with asynchronous handlers. The HTTP runtime invokes the BeginProcessRequest method as illustrated by the following pseudocode:

// Sets an internal member of the HttpContext class with 
// the current instance of the asynchronous handler
context.AsyncAppHandler = asyncHandler;

// Invokes the BeginProcessRequest method on the asynchronous HTTP handler
asyncHandler.BeginProcessRequest(context, OnCompletionCallback, context);

The context argument is the current instance of the HttpContext class and represents the context of the request. A reference to the HTTP context is also passed as the custom data sent to the handler to process the request. The extraData parameter in the BeginProcessRequest signature is used to represent the status of the asynchronous operation. The BeginProcessRequest method returns an object of type HttpAsyncResult—a class that implements the IAsyncResult interface. The IAsyncResult interface contains a property named AsyncState that is set with the extraData value—in this case, the HTTP context.

The OnCompletionCallback method is an internal method. It gets automatically triggered when the asynchronous processing of the request terminates. The following listing illustrates the pseudocode of the HttpRuntime private method:

// The method must have the signature of an AsyncCallback delegate
private void OnHandlerCompletion(IAsyncResult ar) 
{
    // The ar parameter is an instance of HttpAsyncResult 
    HttpContext context = (HttpContext) ar.AsyncState;

    // Retrieves the instance of the asynchronous HTTP handler 
    // and completes the request
    IHttpAsyncHandler asyncHandler = context.AsyncAppHandler;
    asyncHandler.EndProcessRequest(ar);

    // Finalizes the request as usual 
    ...
}

The completion handler retrieves the HTTP context of the request through the AsyncState property of the IAsyncResult object it gets from the system. As mentioned, the actual object passed is an instance of the HttpAsyncResult class—in any case, it is the return value of the BeginProcessRequest method. The completion routine extracts the reference to the asynchronous handler from the context and issues a call to the EndProcessRequest method:

void EndProcessRequest(IAsyncResult result);

The EndProcessRequest method takes the IAsyncResult object returned by the call to BeginProcessRequest. As implemented in the HttpApplication class, the EndProcessRequest method does nothing special and is limited to throwing an exception if an error occurred.

Implementing Asynchronous Handlers

Asynchronous handlers essentially serve one particular scenario—when the generation of the markup is subject to lengthy operations, such as time-consuming database stored procedures or calls to Web services. In these situations, the ASP.NET thread in charge of the request is stuck waiting for the operation to complete. Because the thread is a valuable member of the ASP.NET thread pool, lengthy tasks are potentially the perfect scalability killer. However, asynchronous handlers are here to help.

The idea is that the request begins on a thread-pool thread, but that thread is released as soon as the operation begins. In BeginProcessRequest, you typically create your own thread and start the lengthy operation. BeginProcessRequest doesn't wait for the operation to complete; therefore, the thread is returned to the pool immediately.

There are a lot of tricky details that this bird's-eye description just omitted. In the first place, you should strive to avoid a proliferation of threads. Ideally, you should use a custom thread pool. Furthermore, you must figure out a way to signal when the lengthy operation has terminated. This typically entails creating a custom class that implements IAsyncResult and returning it from BeginProcessRequest. This class embeds a synchronization object—typically a ManualResetEvent object—that the custom thread carrying the work will signal upon completion.

In the end, building asynchronous handlers is definitely tricky and not for novice developers. Very likely, you are more interested in asynchronous pages than in asynchronous HTTP handlers—that is, the same mechanism but applied to .aspx resources. In this case, the "lengthy task" is merely the ProcessRequest method of the Page class. (Obviously, you configure the page to execute asynchronously only if the page contains code that might start I/O-bound and potentially lengthy operations.)

Starting with ASP.NET 2.0, you find ad hoc support for building asynchronous pages more easily and comfortably. An introductory but still practical chapter on asynchronous pages can be found in my book Programming ASP.NET Applications—Advanced Topics (Microsoft Press, 2006).

Warning I've seen several ASP.NET developers using an .aspx page to serve markup other than HTML markup. This is not a good idea. An .aspx resource is served by quite a rich and sophisticated HTTP handler—the System.Web.UI.Page class. The ProcessRequest method of this class entirely provides for the page life cycle as we know it—Init, Load, and PreRender events, as well as rendering stage, view state, and postback management. Nothing of the kind is really required if you only need to retrieve and return, say, the bytes of an image.

< Back      Next >