Practical ASP.NET

Create Dedicated Service Handlers

Creating your own HTTP handler in ASP.NET 1.1 or 2.0 gives you a flexible and fast way to move data to clients, browsers, and other Web pages.

Web Services provide a consistent, standards-based way to provide Web-based access to server resources.

That said, you might be better off creating an ASP.NET HTTP handler when performance is paramount and access is restricted to internal clients. And an ASP.NET handler outperforms every other option in ASP.NET when it comes to delivering data to the client over the Web.

In addition to providing high-efficiency access to server-side resources, an ASP.NET handler provides you with more flexibility in integrating those resources into Web, client-server, and desktop applications. You can call a handler from a page's HTML, server-side code, or client-side code. You can even call a handler from a Windows Forms client. The best part: They are easy to write.

An ASP.NET handler outperforms all of your alternatives in each of the scenarios described (see Table 1).

A handler gains efficiency through what it lacks. Think of an ASP.NET handler as an ASPX file, one that fires no events and has no HTML to render. These missing elements reduce a handler's overhead and give it a performance advantage over an ASP.NET Web Form.

The first step in creating your handler is to add a new file with the extension ASHX to your ASP.NET Web site (see Figure 1). This file must contain a WebHandler processing directive and, if you are going to put your code in the ASHX file itself, include the Language and Class attributes. This example defines a handler that you write in Visual Basic, defining a class named EmployeeInfo:

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

ASP.NET creates no code file for an ASHX file by default. Many programmers prefer to place their code in a separate file, especially in Visual Studio 2003 where code in an ASHX file doesn't have IntelliSense, code highlighting, or debugging support. To use a code file with your ASHX file, you must add a CodeBehind attribute to the ASHX file's WebHandler directive and set the attribute to the name of your code file. You then add a Class file with that name to your Web site (Visual Studio 2005 will place the file in your project's App_Code folder). You no longer place the code in the ASHX file, so you can remove the Language attribute from the WebHandler directive:

<%@ WebHandler Class="EmployeeInfo" 
CodeBehind="EmployeeInfo.vb" %>

The code for your handler must implement the IHttpHandler interface. Implementing this interface requires you to add code for one method (ProcessRequest) and one property (IsReusable).

Improve Scalability
You can improve your application's scalability by having the IsReusable property return True. This causes ASP.NET to cache your handler after the first user requests it. If your handler will tie up some limited resource or be used infrequently, you should return False from this property.

You must also specify the content type your handler returns by using the ContentType property of the Context object that ASP.NET passes to the ProcessRequest method. You can test your handler just by making your ASHX file your start page and pressing the F5 key if you set the ContentType property to "text/HTML." Your handler's starting point should like this after you make these changes:

Imports System
Imports System.Web

Public Class EmployeeInfo
Implements IHttpHandler
Public Sub ProcessRequest(ByVal context _
As HttpContext) Implements _
IHttpHandler.ProcessRequest
Context.Response.ContentType = "text/HTML"
End Sub
Public ReadOnly Property IsReusable() As Boolean _
Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
End Class

So far, you've created the skeleton for your handler, and put your business logic in the ProcessRequest method of your handler (ASP.NET calls this method automatically whenever a client requests your handler). You also have access to a number of existing ASP.NET objects through the Context object passed to the ProcessRequest method, including the Request, Response, and User objects.

The simplest possible ProcessRequest returns a string by using the Response object's Write method:

Public Sub ProcessRequest(ByVal context _
As HttpContext) Implements _
IHttpHandler.ProcessRequest

context.Response.ContentType = _
"text/HTML"
context.Response.Write("Hello World")
End Sub

You can also use the Request object to access any of the information sent up to the server by the client as part of the request. This code in the ProcessRequest routine pulls the value of a parameter called "name" from the querystring and incorporates it into the string you write back to the browser:

context.Response.Write("Hello, " & _
context.Request.QueryString("name"))

You can request the handler by entering its URL into the address box of any browser. A typical request for this handler might look like this:

http://MyServer/MyHandlerSite/
EmployeeInfo.ashx?name=Peter

You can request the handler using only an URL, which enables you to embed the URL into any Web page. Using an URL for your handler, along with any querystring parameters, makes it an attribute of a tag that normally holds a URL. This causes a browser to incorporate the text returned by the handler into the page. This code puts the URL in the src attribute of an iframe tag:

<form id="form1" runat="server">
<div>
<iframe src= _
"EmployeeInfo.ashx?name=Peter" />
</div>
</form>

Taking advantage of the Response object's methods for transferring the contents of files and streams to the client enables you to build a handler that lets a client request a file from the server. For example, you can use the WriteFile method to transfer a file's contents directly to the output stream without buffering. This code uses the name in the QueryString to build the name of a graphic file to send to the client:

context.Response.WriteFile( _
context.Request.QueryString("name") & ".jpg")

You can call this handler from the src attribute of an image tag. This code adds the contents of an image file to a page:

<img src="Photo.ashx?EmployeeName=davolio" 
alt="Some employee's picture" />

Integrate Your Handler
You can also integrate your handler into the surrounding application by accessing user data in the Session object from your handler's code. Unfortunately, by default, you get an "Object reference not set to an instance of an object" message if you attempt to use the Session property on the Context object. Implementing the IRequiresSessionState interface enables you to gain access to the Session object. This interface doesn't require you to implement any methods or properties—its presence signals to ASP.NET that it needs to instantiate the Session object:

Public Class EmployeeInfo
Implements IHttpHandler
Implements IRequiresSessionState

This more complex example reads data from the Northwind database to build an HTML table of employee information. Note that you don't pull information from the querystring. Instead, use a value in the Session object to determine which employee's information to display. The code builds an img tag to display the employee photo pointed to by the PhotoPath field (see Listing 1 and Figure 2).

These examples discussed so far play with text at a basic level. However, you can also use a handler to send binary data to the client, assuming that the client can deal with the data. In the Northwind database's Employee's table, the Photo column is a BLOB field containing the employee's photo. Fortunately, in a browser, the <img> tag expects to receive a string of binary data. All your handler must do is extract the binary data from the field and send it to the client.

You need to read a binary field twice using the DataReader's GetBytes method in order to retrieve its contents. First, you call the GetBytes method with Nothing as its third parameter to determine the number of bytes in the field. You then use that value to dimension a buffer that to hold the binary data. With the buffer dimensioned, you call GetBytes again, passing the buffer as the third parameter to load the buffer with data from the field.

Begin by retrieving the record for the employee in the Session object:

Dim Photo() As Byte
Dim lngLength As Long
Dim sbOutPut As New StringBuilder
Dim con As New SqlConnection( _
ConfigurationManager.ConnectionStrings( _
"NwindConnection").ConnectionString)
Dim cmd As SqlCommand = con.createcommand
cmd.CommandText = _
"Select Photo " & _
" Where LastName ='" & _ 
context.Session("EmployeeName") & "';"

Next, extract the Photo field's data:

con.Open()
Dim rdr As SqlDataReader = cmd.ExecuteReader
If rdr.Read() Then
lngLength = rdr.GetBytes(0, 0, Nothing, 0, _
Integer.MaxValue) - 1
ReDim Photo(lngLength)
rdr.GetBytes(0, 0, Photo, 0, lngLength)
con.Close

The Response object's OutputStream provides a mechanism to stream data to the client. You use the Write method of the OutputStream to transfer data from the buffer to the client. Note that you must skip the first 78 bytes of the Photo field in order to get valid output for the <img> tag (for reasons unknown to me):

context.Response.OutputStream.Write( _
Photo, 78, lngLength - 78)
End If

Use this tag to call your handler and display the contents of the Photo field in a Web page:

<img src="EmployeePhoto.ashx" 
alt="Empoyee photo" />

Beyond HTML
You're not limited to calling your handler from HTML tags. You can use the XMLHTTP object to call your handler if you need to process the result returned from your handler with client-side code. This IE-specific code uses the XMLHTTP object to call a handler and catch the result:

var xmlhttp = new ActiveXObject( 
"Microsoft.XMLHTTP");
xmlhttp.open("GET", 
"http://MyServer/ MyHandlerSite/
EmployeeInfo.ashx?name=Davolio", 
false);
xmlhttp.Send(null);
var result = xmlhttp.responseText;

This version of the code works in recent versions of FireFox, Mozilla, Safari, and Netscape:

var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET",
"http://MyServer/ 
MyHandlerSite/EmployeeInfo.ashx?name=
Davolio", false);
xmlhttp.send(null);
var result = xmlhttp.responseText;

You can also call a handler from server-side code in a WebForm by using the WebClient object. The DownloadString method of the WebClient object accepts the URL for the handler and returns the handler's result as a string:

Dim wc As New System.Net.WebClient
Me.TextBox1.Text = wc.DownloadString( _
"http://MyServer/MyHandlerSite/" & _
"EmployeeInfo.ashx?name=Peter")

Note that you shouldn't call your handler from a page on the same site because doing so is less efficient. Instead, you should create an object with the necessary functionality and then add a handler to act as a fa├žade for that object. This design enables you to access the object directly from the server-side code of WebForms on the same site.

The WebClient object is still useful. You can use the WebClient-based code from other sites or in a Windows Form client to retrieve data from your ASP.NET handler. The WebClient and XMLHTTP objects effectively provide any client with a highly efficient way of retrieving data from your Web application. Clients don't need to create a proxy with a handler, in contrast to using clients with a Web Service. Clients also give you more ways to integrate calls into their code.

Nothing is free, however. If you move away from Web Services, you also have to give up the standards provided by the Web Services specification. You won't be able to count on a Web Service's WSDL file to define your message formats, but instead create and process a set of custom formats. Handlers are efficient, but you should use them only to transfer data when you are also creating the clients that access the handlers.

You now have everything that you need to implement a synchronous ASP.NET handler. But you can also create asynchronous handlers that will provide better scalability than the synchronous handler discussed in this column previously. One warning: Asynchronous handlers are more complex to write than a synchronous handler. In order to take advantage of asynchronous processing, you will need to launch a new thread and create a new class that returns a custom IAsyncResult object.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.