Leveraging httpHandlers to Stream Custom Content in ASP.NET

All requests in the ASP.NET pipeline are ultimately processed by an httpHandler. Each handler has a specific job to produce content to the browser, based on the request type and any parameters passed to the server. There are several built-in httpHandlers in the ASP.NET framework. For example, the System.Web.UI.Page class implements the httpHandler interface. The Page class processes all requests with the .aspx extension and produces the appropriate HTML markup to the browser.

All custom ASP.NET Handlers implement the IhttpHandler interface. This interface defines a method, ProcessRequest, and a property, IsReusable, that must be implemented by any custom handler. The IsReusable property returns either true or false, indicating if the handler can be used simultaneously or not. The ProcessRequest method is called by the .NET engine, passing a reference to the current request’s HttpContext. This is where any custom processing is done.

The IsReusable property indicates to the run time if a handler can be reused by another request. Typically this should return false, and the instance of the handler will not be pooled. Generally, for any handler that generates dynamic content, the IsReusable property should return false. If the handler returns the same content or is asynchronous, it can return true.

Custom handlers can be associated with specific file extensions or called directly as a URL. The .NET framework allows us to either create a custom class for a handler or use a generic handler that ends with the .ashx extension. While there are no hard and fast rules on which file type should be used, it will often be apparent as you build your applications.

Custom httpHandlers differ from custom httpModules in many ways, but are often confused by many ASP.NET developers. Modules are designed to hook into the ASP.NET processing pipeline to adjust the request as needed, while handlers process the request to build the content being sent to the browser. Although they are complementary, they serve two completely separate functions.

Built-in httpHandlers

By definition, .NET leverages some basic httpHandlers to process requests. Every ASP.NET page is processed by the System.Web.UI.Page class, which implements the IHttpHandler interface. The Page class actually executes the request on the server to produce the final markup sent to the browser. You can examine the code for the Page class, either by using Reflector or by using access to the .NET framework source code via the new Microsoft reference license. Recently Microsoft released access to the .NET Framework’s source code through a reference-only license, so developers could actually step into the framework’s source during debugging. Some of the built-in httpHandlers are listed in Table 1.

Table 1

Handler Description
ASP.NET Page–.aspx Implemented by the System.Web.UI,.Page class as the main foundation of any ASP.NET page request
Web Service–.asmx Processes web service requests
Trace–.axd Processes requests for trace information

Adding an httpHandler to an ASP.NET Web Site

Visual Studio allows you to add a custom httpHandler either by adding a generic handler file or a new class file. Determining which method is better depends on the ultimate use of the handler. If you choose to map a specific file extension or set of file extensions to the handler, then it would be better to create a custom class that implements the IHttpHandler interface. If, instead, you plan on directly referencing the handler via a URL, then you should add a generic handler file.

How to Add a Custom Handler Class

To add a new custom class to a Visual Studio web site, right-click on the root of the web site and select “Add New Item.” Once the “Add New Item” dialog is opened, select a Class (see Figure 1). If you do not have an App_Code folder in your web site, you will be prompted to add one. Select OK. Once the class is opened in Visual Studio, add the Implements IHttpHandler to the class definition or IHttpHandler in C# to implement the IHttpHandler interface.

Example Class Declarations

VB

Public Class BasicHTMLHandler

    Implements IHttpHandler

C#

Public Class BasicHTMLHandler : IHttpHandler

Adding a Generic Handler File

A generic handler file can also be added to the web site by opening the “Add New Item” dialog. Selecting “Generic Handler” will add an .ashx file to the site (see Figure 2). This file can then be referenced directly via a URL, but gives you the flexibility of programming the same way as a custom class would.

Registering httpHandlers

A custom httpHandler class needs to be registered or mapped to execute when a file type is requested. This is done in the web.config file. Within the System.Web element, the httpHandlers element is used to add and remove handlers for specific extensions.

The section can contain add, clear, and remove child elements. The add element registers a custom handler to process requests for specified file extensions and action verbs. The clear element removes any registered handlers the site inherits, such as from the machine.config file. The remove element removes a specific registered handler. The following example is included with the base web-site template in Visual Studio.

Registering Custom Handlers in the web.config File

        <httpHandlers>

            <remove verb="*" path="*.asmx"/>

            <add verb="*" path="*.asmx" validate="false"

type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

            <add verb="*" path="*_AppService.axd" validate="false"

type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

            <add verb="GET,HEAD" path="ScriptResource.axd"

type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>

            <add verb="*" path ="*.png" validate ="false"

type="ProtectedImageHandler"/>

            <add verb="*" path ="*.html" validate ="false"

type="BasicHTMLHandler"/>

            <add verb="*" path ="*.pdf" validate ="false"

type="GetPDFs"/>

<httpHandlers> Mapping File Extensions to ASP.NET

Although the built-in development web server that Visual Studio uses by default processes all requests through the ASP.NET engine, this is not true with IIS. File types are mapped through specific ISAPI filters in IIS 6 and before, such as the aspnet.dll. While IIS 7 still supports ISAPI filters, actual extensions are assigned through handler mappings. This is because IIS 7 allows custom httpHandlers to be mapped for the entire server within IIS and not just the web site, allowing you to create and map handlers that can process requests for the entire server and not just a specific site.

Register a File Extension in IIS 6

In IIS 6, you map file extensions to the appropriate ISAPI filter by selecting the configuration from the “Home Directory” tab in the specific site’s Property dialog. You can also map file extensions for the entire server by opening the Properties dialog on the “Web Sites” node in the IIS 6 manager. This will open the “Application Configuration” dialog, which has three tabs (Figure 3). The first tab is Mappings, where file extensions are mapped to the appropriate ISAPI filter. Mappings can be added, edited, or removed.

The “Add/Edit Application Extension Mapping” dialog (Figure 4) has several options that need to be addressed. First is the Executable or ISAPI extension. For ASP.NET 2.0, it is the aspnet_isapi.dll. Next, the actual file extension needs to be included; for example, if you want jpegs to be processed by a handler, then enter .jpg. The Verbs section will typically be “All Verbs,” but there may be occasions upon which you only want to allow specific verbs, such as POST, for security or functionality reasons.

If you are serving files that do not actually exist, then uncheck the “Verify that file exists” option. This tends to be an issue for developers who are streaming images from a database or other virtual content. The “Script engine” option is used when you want to execute the request in a directory where execute permissions do not exist. This option is typically used for script engines such as Classic ASP or PHP.

Registering a Handler Mapping in IIS 7

IIS 7 drastically differs from IIS 6 because it is so tightly integrated with the ASP.NET framework. Because of this coupling, custom httpHandlers can now be written to handle specific file extensions at the server level, instead of ISAPI filters. While ISAPI filters are still supported, building custom handlers for a particular file type should be much easier using the .NET framework over the traditional C or C++ unmanaged ISAPI filter.

Mapping a custom handler to a file type in IIS 7 takes just a few clicks, starting with opening the Handler Mappings (Figure 5) section from the IIS Admin Home. On the Handler Mappings page, there is a grid of mapped handlers displaying the handler Name, extension (Path), enabled State, Path Type, the Handler itself, and the Entry Type.

Figure 5: Handler Mappings page.

image

A custom Handler needs to be compiled into an assembly and stored in the system’s GAC before it can be mapped in IIS. Mapping a custom Handler is done from the Actions pane (right column, Figure 6) by clicking the “Add Managed Handler” link.

Figure 6: Handler Mappings Actions pane.

image

The “Add Managed Handler” dialog (Figure 7) will display. The Requested path field should be the file extension, .aspx, for example. In the Type dropdown, select your custom Handler class. All custom Handlers registered in the GAC will be listed. In the Name field, enter a friendly name for the custom Handler.

Figure 7: Add Managed Handler dialog.

image

The “Request Restrictions” button displays another dialog (Figure 8) to set specific restrictions on when the handler should be fired. The mapping tab is used to restrict the handler to only fire when it meets the criteria of a File, Folder, or Both. By default, this is not enabled. The Verbs dialog allows you to specify that the handler only be invoked when specific request Verbs (Get, Post, Head, etc.) are met. By default, this is set to “All verbs.”

Figure 8: Managed Handler Request Restrictions dialog.

image

Once you have selected all the settings for the handler mapping, simply click OK on the dialog(s), and your mapping should be in effect for the entire web server.

A Basic HTML Handler

A common example to demonstrate how httpHandlers work is to return some basic HTML to the browser when the handler is requested or executed. This is a good place to start learning how httpHandlers function because it is the most empirical demonstration.

Add a new class, called BasicHTMLHandler, to an ASP.NET web site. If you do not have an App_Code folder, you will be asked if you want to add one. It is a good idea to accept an App_Code folder because it organizes your code. Once the class file is displayed in Visual Studio, add Implements IhttpHandler (VB) or :IhttpHandler (C#) after your class declaration, and press Return. This will create empty methods for ProcessRequest and IsReusable in VB. The C# experience requires more work. You can either create your implementation methods by hand or select the methods from the menus at the top of the page.

The ProcessRequest method accepts an httpContext parameter that represents the context of the request. In turn, this reference provides access to the Request and Response objects that can be used to analyze the request made by the user and supply content to be sent to the browser in response.

It is a good practice to validate the existence of these objects before proceeding with any processing because if the context, request, or response objects are null, you will have more exceptions as you process the request. This can be done by checking to see if they are null or nothing, and if they are throwing an ArgumentNullException with an appropriate message. Throwing an exception at this point is a good idea instead of trying to send a clean Error page to the user, because the actual means to send an error message may not exist.

Once the Response Object has been verified, you can start adding content to the output stream. This can be done with the Write method familiar to most Classic ASP and ASP.NET developers. The most common use of this method is to pass a string to be written to the browser. In addition to streaming HTML content, response headers can also be set, such as the ContentType, ContentEncoding, and Caching. Finally, the Flush method pushes all the content in the response buffer to the client, and the End method terminates the response process.

A Simple HTML Handler Class

VB

Imports Microsoft.VisualBasic

 

Public Class BasicHTMLHandler

    Implements IHttpHandler

 

 

Public ReadOnly Property IsReusable() As Boolean Implements

       System.Web.IHttpHandler.IsReusable

        Get

            Return True

        End Get

    End Property

 

    Public Sub ProcessRequest(ByVal context As System.Web.HttpContext)

         Implements System.Web.IHttpHandler.ProcessRequest

 

        If IsNothing(context) Then

            Throw New ArgumentNullException("There is no HttpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException("There is no HttpContext.Response

            Associated with the Request.")

        End If

 

        Dim Response As HttpResponse = context.Response

 

        Response.ClearContent()

        Response.ContentType = "text/html"

        Response.ContentEncoding = Text.Encoding.UTF8

        Response.Cache.SetCacheability(HttpCacheability.NoCache)

 

        Response.Write("<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0

Transitional//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">")

        Response.Write("<html xmlns=""http://www.w3.org/1999/xhtml"">")

        Response.Write("<head>")

        Response.Write("<title>Custom HttpHandlers</title>")

        Response.Write("</head><body>")

        Response.Write("<h1 align=""center"">Welcome to the Magical World of HttpHandlers</h1>")

        Response.Write("</body></html>")

 

        Response.Flush()

        Response.End()

 

    End Sub

 

End Class

C#

using System;

using System.Web;

 

public class BasicHTMLHandler : IHttpHandler

{

 

    public bool IsReusable

    {

        get { return true; }

    }

 

    public void ProcessRequest(System.Web.HttpContext context)

    {

 

        if ((context == null))

        {

            throw new ArgumentNullException("There is no HttpContext

                   Associated with the Request.");

        }

 

        if ((context.Response == null))

        {

            throw new ArgumentNullException("There is no

httpContext.Response Associated with the Request.");

        }

 

        HttpResponse Response = context.Response;

 

        Response.ClearContent();

        Response.ContentType = "text/html";

        Response.ContentEncoding = System.Text.Encoding.UTF8;

        Response.Cache.SetCacheability(HttpCacheability.NoCache);

 

        Response.Write("<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0

Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">");

        Response.Write("<html xmlns="http://www.w3.org/1999/xhtml">");

        Response.Write("<head>");

        Response.Write("<title>Custom HttpHandlers</title>");

        Response.Write("</head><body>");

        Response.Write("<h1 align="center">Welcome to the Magical World of HttpHandlers</h1>");

        Response.Write("</body></html>");

 

        Response.Flush();

        Response.End();

 

    }

 

}

Synchronous httpHandlers

Most httpHandlers process a full request and are called synchronous handlers. In a synchronous request, the ProcessRequest method is executed, and any markup produced is sent to the browser once the entire request is processed.

Asynchronous httpHandlers

Some requests depend on a long-running task, such as returning data from a third-party service. An asynchronous handler has two methods that execute instead of the ProcessRequest method of a synchronous handler. An asynchronous handler uses the BeginProcessRequest and the EndProcessRequest methods to begin and end a request.

A Simple HTML Handler

The core task of a handler is to return HTML markup to the browser. The ProcessRequest method is called to initiate the handler’s operation. What is actually done once the handler has been invoked is all up to the developer. A handler can be used to return any HTML, which is what the Web is all about.

A simple handler can be used to demonstrate registering a handler for a file type, .html, and return basic HTML. This idea can be extended to return dynamic HTML for a request instead of a fixed file. In ASP.NET 2.0 and above, the concept of a VirtualFileProvider was introduced. This allows you to store fixed markup in a data source, such as SQL Server. For even more on this concept, you can research the VirtualFileProvider. This technique is leveraged by SharePoint and the MSDN online documentation.

RSS Handler

Over the past decade, Real Simple Syndication (RSS) has become a very popular way to distribute content across the Web, especially with the rise in blogging. RSS is an XML feed that a site maintains with a list of available URLs in the site and some content to describe the resource. Since this content can change on a regular basis, a custom httpHandler is a perfect way to dynamically build this feed for RSS readers to consume.

This example does not get into the details of building the actual RSS XML, but instead demonstrates the core features needed for a custom handler to serve a feed. There are three important pieces a custom RSS handler needs to serve: ContentType, ContentEncoding, and, of course, the RSS XML.

The ProcessRequest method verifies that important objects exist first, before processing the request to serve the RSS feed. The example checks to ensure that the context object is not null before proceeding. If it is null, an ArgumentNullException is thrown with a message to alert the user or the developer where the issue resides. Similarly, the response object is also checked, and an ArgumentNullException is thrown if there is no response object.

It is a good practice to verify any sort of object or parameter needed to process a request. Not only should this be used for the context object, but also any required QueryString or Form variables should also be verified before processing them. There are some examples of this below in this Wrox Blox.

Next, class-level variables are set for both the context and response objects. By doing this, we do not have to pass references to any worker functions. The actual mechanism to build and send the RSS feed is done within the WriteRSS method.

The WriteRSS method manages the building of the RSS feed and sets any Response headers needed for the client to process. Since RSS is an XML document, the ContentType (MIME type) is set to “text/xml.” This tells the browser or RSS reader how to render the content. The ContentEncoding will typically be UTF8, which is a Unicode encoding type that is compatible with ASCII. It is becoming the de facto standard for transmitting text around the Internet.

To send the RSS XML, the Response.Write method is called, passing in the XML string. This XML can be built by any means needed in the site. For the example, it just reads a text file with some pre-built RSS XML in it, but typically, this would be built using some sort of data feed that is converted to XML. This is refactored into the GetRSS method.

Serve an RSS Feed with a Custom Handler

VB

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

 

Imports System

Imports System.Web

Imports System.IO

 

Public Class RSS : Implements IHttpHandler

 

#Region " Properties "

 

    Private _context As HttpContext

    Public Property CurrentContext() As HttpContext

        Get

            Return _context

        End Get

        Set(ByVal value As HttpContext)

            _context = value

        End Set

    End Property

 

    Private _Response As HttpResponse

    Public Property Response() As HttpResponse

        Get

            Return _Response

        End Get

        Set(ByVal value As HttpResponse)

            _Response = value

        End Set

    End Property

 

#End Region

 

 

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

 

        'Check to ensure there is a Context and Response Object.

        If IsNothing(context) Then

            Throw New ArgumentNullException(                   "There is no httpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException(                   "There is no httpContext.Response Associated with the Request.")

        End If

 

        CurrentContext = context

        Response = context.Response

 

        'Actually create and send the RSS feed to the client.

        WriteRSS()

 

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean            Implements IHttpHandler.IsReusable

        Get

            Return False

        End Get

    End Property

 

 

    Public Sub WriteRSS()

 

        Response.ContentType = "text/xml"

        Response.ContentEncoding = Text.Encoding.UTF8

        Response.Write(GetRSS)

        Response.End()

 

    End Sub

 

    Public Function GetRSS() As String

 

        'This is an over simplified way to serve RSS.

        'To keep the code simple and focused on the          httpHandler aspects it just loads a pre-done text file.

        Dim st As Stream = _

            File.Open(Path.Combine(                    CurrentContext.Request.PhysicalApplicationPath,

                   "rss.txt"), _

                   FileMode.Open, FileAccess.Read)

        Dim str As New StreamReader(st)

        Dim sRSS As String = str.ReadToEnd

 

        st.Close()

        str.Close()

 

        Return sRSS

    End Function

 

End Class

C#

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

 

 

using System;

using System.Web;

using System.IO;

 

public class GetRSS : IHttpHandler

{

 

    #region " Properties "

 

    private HttpContext _context;

    public HttpContext CurrentContext {

        get { return _context; }

        set { _context = value; }

    }

 

    private HttpResponse _Response;

    public HttpResponse Response {

        get { return _Response; }

        set { _Response = value; }

    }

 

    #endregion

 

    public void ProcessRequest(HttpContext context)

    {

 

        //Check to ensure there is a Context and Response Object.

        if ((context == null)) {

            throw new ArgumentNullException(                    "There is no httpContext Associated with the Request.");

        }

 

        if ((context.Response == null)) {

            throw new ArgumentNullException(                    "There is no httpContext.Response Associated with the Request.");

        }

 

        CurrentContext = context;

        Response = context.Response;

 

        //Actually create and send the RSS feed to the client.

        WriteRSS();

 

    }

 

    public bool IsReusable {

        get { return false; }

    }

 

 

    public void WriteRSS()

    {

 

        Response.ContentType = "text/xml";

        Response.ContentEncoding = System.Text.Encoding.UTF8;

        Response.Write(GetRSSContent());

        Response.End();

 

    }

 

    public String GetRSSContent()

    {

 

        //This is an over simplified way to serve RSS.

        //To keep the code simple and focused on the httpHandler

           aspects it just loads a pre-done text file.

        Stream st = File.Open(Path.Combine            (CurrentContext.Request.PhysicalApplicationPath, "rss.txt"), FileMode.Open,            FileAccess.Read);

        StreamReader str = new StreamReader(st);

        string sRSS = str.ReadToEnd();

 

        st.Close();

        str.Close();

 

        return sRSS;

    }

 

}

A vCard Handler

One of the common uses of custom httpHandlers is to produce dynamic content for odd documents. An example would be a vCard, which is commonly used to represent contact information about a person. These cards have their own RFC (2426) and are not Microsoft-specific, but they will work with Microsoft Outlook and many other contact managers. Serving vCards from a handler provides a dynamic way to manage access to this information. The data source for a vCard can be anything, like Active Directory, Global Address List, or a site database.

For this example, I am using a vCard class available at www.codeproject.com/KB/vb/vcard.aspx.

The Response.ClearContent method call flushes any data that are currently in the response object, such as headers or data that may have already been supplied.

Just like the RSS handler, the vCard Handler is a generic handler type but could just as easily be a custom class that is mapped to the vcf (the common vCard) extension. This example checks to see if a username was passed in the QueryString. If there was no username passed, it will send a nice message to the user to let him or her know what was expected. You also could have thrown an exception, but in this case, it is a better idea to let the user know what happened in a nicer way than with a scary exception message.

If a username is supplied, then the handler gathers the contact information and builds the vCard. In this example, the vCard is built manually, but it could have just as easily been built from a data source. This handler sets the ContentType to “text/v-card” to let the browser know how to render the content, which will be a dialog prompting the user to either save the vCard locally or open in the default application.

Again, set the Encoding type to UTF8, and then pass the text to the browser using the Response.Write method. Response.Flush will force any content remaining in the Response buffer to be sent to the client. The Response.End method ends the execution of the Response and prevents any additional changes to the response stream.

Return a vCard with a Custom httpHandler

VB

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

 

Imports System

Imports System.Web

 

Public Class vCardHandler : Implements IHttpHandler

 

#Region " Properties "

 

    Private _context As HttpContext

    Public Property CurrentContext() As HttpContext

        Get

            Return _context

        End Get

        Set(ByVal value As HttpContext)

            _context = value

        End Set

    End Property

 

    Private _Response As HttpResponse

    Public Property Response() As HttpResponse

        Get

            Return _Response

        End Get

        Set(ByVal value As HttpResponse)

            _Response = value

        End Set

    End Property

 

#End Region

 

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

 

        'Check to ensure there is a Context and Response Object.

        If IsNothing(context) Then

            Throw New ArgumentNullException(                   "There is no httpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException(                   "There is no httpContext.Response Associated with the Request.")

        End If

 

        CurrentContext = context

        Response = context.Response

 

        Dim sUserName As String = String.Empty

        context.Response.ClearContent()

 

        If Not IsNothing(context.Request.QueryString("username")) Then

            'Do something to retrive the user info from data store.

            '

            '

            '

 

            'More Info: http://tools.ietf.org/html/rfc2426

            context.Response.ContentType = "text/x-vcard"

 

            Dim vc As New vCard()

            vc.FirstName = "Chris"

            vc.LastName = "Love"

            vc.URLs.Add(New vCard.vURL("http://www.professionalaspnet.com"))

            vc.Emails.Add(New vCard.vEmail("[email protected]"))

            Dim _address = New vCard.vAddress()

            _address.AddressName = "Home Office"

            _address.City = "Willow Spring"

            _address.State = "NC"

            _address.Zip = "27592"

 

            vc.Addresses.Add(_address)

 

            context.Response.ContentEncoding = Text.Encoding.UTF8

            context.Response.Write(vc.ToString)

            context.Response.Flush()

            context.Response.End()

 

        Else

            'Instead of throwing an error, let the user know what they             did wrong in a graceful manner.

            context.Response.ContentType = "text/plain"

            context.Response.Write("<P>Sorry, Please supply a valid username.</P>")

            context.Response.Flush()

            context.Response.End()

            Exit Sub

        End If

 

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean          Implements IHttpHandler.IsReusable

        Get

            Return False

        End Get

    End Property

 

End Class

C#

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

 

using System;

using System.Web;

 

public class vCardHandler : IHttpHandler

{

 

    public void ProcessRequest(HttpContext context)

    {

 

        string sUserName = string.Empty;

        context.Response.ClearContent();

 

        if ((context.Request.QueryString["username"] != null)) {

        }

        //Do something to retrieve the user info from data store.

        //

        //

        //

        else {

            context.Response.ContentType = "text/HTML";

            context.Response.Write("<P>Sorry, Please supply a valid username.</P>");

            context.Response.Flush();

            context.Response.End();

            return; // TODO: might not be correct. Was : Exit Sub

        }

 

        //More Info: http://tools.ietf.org/html/rfc2426

        context.Response.ContentType = "text/x-vcard";

 

        vCard vc = new vCard();

        vc.FirstName = "Chris";

        vc.LastName = "Love";

        vc.URLs.Add(new vCard.vURL("http://www.professionalaspnet.com"));

        vc.Emails.Add(new vCard.vEmail("[email protected]"));

        vCard.vAddress _address = new vCard.vAddress();

        _address.AddressName = "Home Office";

        _address.City = "Willow Spring";

        _address.State = "NC";

        _address.Zip = "27592";

 

        vc.Addresses.Add(_address);

 

        context.Response.ContentEncoding = System.Text.Encoding.UTF8;

        context.Response.Write(vc.ToString());

        context.Response.Flush();

        context.Response.End();

 

    }

 

    public bool IsReusable {

        get { return false; }

    }

}

Image Resizer and Cropper Handlers

Manipulating images at run time is a very popular use of custom httpHandlers. Handlers are often used to create a properly sized image for the space where it will be displayed. It is better to send an image in the desired dimensions, rather than the full-sized original image, because it will render faster on the client. A custom httpHandler can be used to resize the image at run time, sending a resized version to the client directly. Of course, resizing the image each time it is requested does consume more resources on the server; thus, a caching mechanism should also be used.

To reduce the number of times the image is requested by the client, the cache should be configured to minimize the number of request operations. This can be done in a custom handler through the Response.Cache object.

Caching content is a way to speed up a web site. There are several places where content can be cached: Sets of data can be cached in server memory, or page content can be cached either on the server or the client. Caching data is out of the scope of this Wrox Blox but should be evaluated for any application.

ASP.NET includes an OutputCache directive for each Page or User Control. This allows the developer to specify how long the rendered content should be cached in memory. When a page uses Output Cache, the resulting HTML from the request is stored in memory for a specified time period, for example, 1 minute. While the most common way to set the OutputCache is through a directive in the page headers, it can also be controlled through the Response.Cache Object.

Not only is the content stored in the server’s memory, but the client receives a max-age value in the Cache Control header that tells the browser how many seconds the content will be valid. This tells the browser to cache the markup on the client machine and to not make the round trip request for the content, thereby speeding up the application by distributing content and reducing the number of actual requests.

    Public Sub SetCaching()

            

        'Set the Cacheability

        Response.Cache.SetCacheability(HttpCacheability.Public)

        'Set the Cache for another 60 seconds.

        Response.Cache.SetExpires(DateAdd(DateInterval.Minute, 1, Now()))

            

    End Sub

The httpCacheability enumeration has six options to specify how caching should be applied to a request. The Enumeration values can range from not caching the content to caching on the server the client and on any proxy servers along the way. More details about the Cacheability enumeration can be found in the MSDN library, http://msdn2.microsoft.com/en-us/library/system.web.httpcacheability.aspx.

The ImageCropper handler adds to the normal verifications to ensure that the required parameters are included in the QueryString. If parameters are not passed, then an ArgumentNullException is manually thrown. In addition to storing a reference to the context and response objects, the Width and Height are stored in class level properties.

An “Action” parameter is used to determine if the image is to be resized or cropped. Each of these operations are refactored into specific methods: ResizeImage and CropImage. The methods set the ContentType based on the image type, “image/jpeg,” for example. Since this is a simple example, it uses a fixed image, which is a JPEG file. Other file types that could be served over the Web include GIF and PNG files. Those ContentTypes would be “image/gif” and “image/png,” respectively.

The image resizing and cropping mechanisms in this example are fairly basic and not relative to the operation of the handler itself. The image is directly streamed to the client by saving the manipulated image directly to the output stream. The Bitmap class uses the Save method and accepts a Stream and an ImageFormat enumeration. The Response object exposes a stream through the OutputStream property. This will push the manipulated image to the browser to be displayed.

A Custom Handler to Resize or Crop an Image

VB

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

          

Imports System

Imports System.Web

Imports System.Drawing

Imports System.IO

 

Public Class ImageCropper : Implements IHttpHandler

 

#Region " Properties "

 

    Private _context As HttpContext

    Public Property CurrentContext() As HttpContext

        Get

            Return _context

        End Get

        Set(ByVal value As HttpContext)

            _context = value

        End Set

    End Property

 

    Private _Response As HttpResponse

    Public Property Response() As HttpResponse

        Get

            Return _Response

        End Get

        Set(ByVal value As HttpResponse)

            _Response = value

        End Set

    End Property

 

 

    Private _width As Integer

    Public Property Width() As Integer

        Get

            If _width = 0 Then

                _width = 200

            End If

            Return _width

        End Get

        Set(ByVal value As Integer)

            _width = value

        End Set

    End Property

 

 

    Private _height As Integer

    Public Property Height() As Integer

        Get

            If _height = 0 Then

                _height = 200

            End If

            Return _height

        End Get

        Set(ByVal value As Integer)

            _height = value

        End Set

    End Property

 

 

#End Region

 

 

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

 

        If IsNothing(context) Then

            Throw New ArgumentNullException("There is no httpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException(             "There is no httpContext.Response Associated with the Request.")

        End If

 

        If IsNothing(context.Request("w")) Then

            Throw New ArgumentNullException("There is no width specified.")

        End If

 

        If IsNothing(context.Request("h")) Then

            Throw New ArgumentNullException("There is no height specified.")

        End If

 

        If IsNothing(context.Request("Action")) Then

            Throw New ArgumentNullException("There is no action specified.")

        End If

 

        CurrentContext = context

        Response = context.Response

        Width = context.Request("w")

        Height = context.Request("h")

 

        Select Case context.Request("Action").ToLower

 

            Case "r"

                ResizeImage()

            Case "c"

                CropImage()

            Case Else

                Throw New ArgumentException(                       "A valid action was not specified, R = Resize, C = Crop.")

        End Select

 

    End Sub

 

    Sub ResizeImage()

        Dim Original As Image = Image.FromFile(_

            Path.Combine(CurrentContext.Request.PhysicalApplicationPath, "to_resize.jpg"))

 

        Dim NewImage As Bitmap

 

        Dim NewWidth As Integer

        Dim NewHeight As Integer

 

        If Original.Width < Original.Height Then

            NewWidth = Original.Width / (Original.Width / Width)

            NewHeight = Original.Height / (Original.Width / Width)

        ElseIf Original.Width > Original.Height Then

            NewWidth = Original.Width / (Original.Height / Height)

            NewHeight = Original.Height / (Original.Height / Height)

        Else

            NewWidth = Original.Width / (Original.Width / Width)

            NewHeight = Original.Height / (Original.Height / Height)

        End If

 

        NewImage = New System.Drawing.Bitmap(Original, NewWidth, NewHeight)

 

        Response.ContentType = "image/jpeg"

 

        SetCaching()

 

        NewImage.Save(Response.OutputStream, Imaging.ImageFormat.Jpeg)

 

    End Sub

 

    Sub CropImage()

        Dim myBitmap As New System.Drawing.Bitmap(_

            Path.Combine(CurrentContext.Request.PhysicalApplicationPath, "to_resize_resized.jpg"))

 

        Dim myBitmapCropped As New System.Drawing.Bitmap(Width, Height)

        Dim myGraphic = System.Drawing.Graphics.FromImage(myBitmapCropped)

 

        Dim Top As Integer = (myBitmap.Width / 2) - (Width / 2)              '(myBitmap.Height / 2) - (Height / 2)

        Dim Left As Integer = (myBitmap.Height / 2) - (Height / 2) '             (myBitmap.Width / 2) - (Width / 2)

        myGraphic.DrawImage(myBitmap,_          New System.Drawing.Rectangle(0, 0,_               myBitmapCropped.Width, myBitmapCropped.Height),_               Top, Left, myBitmapCropped.Width,_               myBitmapCropped.Height,_               System.Drawing.GraphicsUnit.Pixel)

        myGraphic.Dispose()

 

        Response.ContentType = "image/jpeg"

 

        SetCaching()

 

        myBitmapCropped.Save(Response.OutputStream, Imaging.ImageFormat.Jpeg)

 

        myBitmap.Dispose()

        myBitmapCropped.Dispose()

 

    End Sub

 

    'This keeps the Caching consistent between the two methods

    Public Sub SetCaching()

 

        'Set the Cacheability

        Response.Cache.SetCacheability(HttpCacheability.Public)

        'Set the Cache for another 60 seconds.

        Response.Cache.SetExpires(DateAdd(DateInterval.Minute, 1, Now()))

 

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean          Implements IHttpHandler.IsReusable

        Get

            Return False

        End Get

    End Property

 

End Class

C#

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

 

using System;

using System.Web;

using System.Drawing;

using System.IO;

 

public class ImageCropper : IHttpHandler

{

 

    #region " Properties "

 

    private HttpContext _context;

    public HttpContext CurrentContext {

        get { return _context; }

        set { _context = value; }

    }

 

    private HttpResponse _Response;

    public HttpResponse Response {

        get { return _Response; }

        set { _Response = value; }

    }

 

 

    private int _width;

    public int Width {

        get {

            if (_width == 0) {

                _width = 200;

            }

            return _width;

        }

        set { _width = value; }

    }

 

 

    private int _height;

    public int Height {

        get {

            if (_height == 0) {

                _height = 200;

            }

            return _height;

        }

        set { _height = value; }

    }

 

    #endregion

 

    public void ProcessRequest(HttpContext context)

    {

 

        if (context == null) {

            throw new ArgumentNullException(             "There is no httpContext Associated with the Request.");

        }

 

        if (context.Response == null) {

            throw new ArgumentNullException(                   "There is no httpContext.Response Associated with the Request.");

        }

 

        if (context.Request["w"] == null) {

            throw new ArgumentNullException("There is no width specified.");

        }

 

        if (context.Request["h"] == null) {

            throw new ArgumentNullException("There is no height specified.");

        }

 

        if (context.Request["Action"] == null) {

            throw new ArgumentNullException("There is no action specified.");

        }

 

        CurrentContext = context;

        Response = context.Response;

        Width = Convert.ToInt32(context.Request["w"]);

        Height = Convert.ToInt32(context.Request["h"]);

 

        switch (context.Request["Action"].ToLower()) {

 

            case "r":

                ImageResize(Width, Height);

                break;

            case "c":

                CropImage(Width, Height);

                break;

            default:

                throw new ArgumentException(                 "A valid action was not specified, R = Resize, C = Crop.");

                break;

        }

 

    }

 

    public void ImageResize(int Width, int Height)

    {

        Image Original = Image.FromFile(Path.Combine(           CurrentContext.Request.PhysicalApplicationPath, "to_resize.jpg"));

 

        Bitmap NewImage;

 

        int NewWidth;

        int NewHeight;

 

        if (Original.Width < Original.Height) {

            NewWidth = Original.Width / (Original.Width / Width);

            NewHeight = Original.Height / (Original.Width / Width);

        }

        else if (Original.Width > Original.Height) {

            NewWidth = Original.Width / (Original.Height / Height);

            NewHeight = Original.Height / (Original.Height / Height);

        }

        else {

            NewWidth = Original.Width / (Original.Width / Width);

            NewHeight = Original.Height / (Original.Height / Height);

        }

 

        NewImage = new System.Drawing.Bitmap(Original, NewWidth, NewHeight);

 

        //NewImage.Save( _

        // Path.Combine(CurrentContext.Request.PhysicalApplicationPath, "to_resize_resized.jpg"))

 

        Response.ContentType = "image/jpeg";

        NewImage.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

 

    }

 

    public void CropImage(int Width, int Height)

    {

      System.Drawing.Bitmap myBitmap =

        new System.Drawing.Bitmap(

        Path.Combine(

               CurrentContext.Request.PhysicalApplicationPath,

               "to_resize_resized.jpg"));

 

System.Drawing.Bitmap myBitmapCropped = new          System.Drawing.Bitmap(Width, Height);

        System.Drawing.Graphics myGraphic = System.Drawing.Graphics.FromImage(myBitmapCropped);

 

        int Top = (myBitmap.Width / 2) - (Width / 2);

        //(myBitmap.Height / 2) - (Height / 2)

        int Left = (myBitmap.Height / 2) - (Height / 2);

        // (myBitmap.Width / 2) - (Width / 2)

        myGraphic.DrawImage(myBitmap,

            new System.Drawing.Rectangle(0, 0, myBitmapCropped.Width, myBitmapCropped.Height),

                Top, Left, myBitmapCropped.Width, myBitmapCropped.Height,

                    System.Drawing.GraphicsUnit.Pixel);

        myGraphic.Dispose();

 

        Response.ContentType = "image/jpeg";

 

        myBitmapCropped.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

 

        myBitmap.Dispose();

        myBitmapCropped.Dispose();

 

    }

 

    public bool IsReusable {

        get { return false; }

    }

 

}

Because a generic handler was used for the custom handler, it can be directly referenced by an image tag in an HTML page. This allows the IMG tag to reference the handler directly as the source. This is nice, because now you do not have to map image files to the handler class, as well as map them in IIS to use ASP.NET. By not doing this, you allow other images to be processed more efficiently.

A PDF Handler

Handlers can be used to serve protected documents, including documents that are controlled by subscription, that require added security, or simply a nonaccessible location. Serving documents through a custom handler is a great way to control access to them because you can examine the request and the user’s credentials to ensure that the user can actually request the document. It could also be used to log access to the document as well, beyond the normal IIS logs.

The PDF handler is a class that implements the ihttpHandler interface, included in the App_Code folder. It could just as easily be an object in a class library project. Because this is an object, it needs to be registered with the site to process specific requests (in this case, all requests are for PDF files). It could be limited to request specific documents, but in this case it will process all PDF requests to the site. This is done in the httpHandlers element of the web.config file.

<add verb="*" path ="*.pdf" validate ="false" type="GetPDFs"/>

The handler is added to the site with an add element that specifies the verb, path to the document, if it should be validated, and the class type (GetPDFs). The verb can be Get, Post, Put, or Head. A Get request is one in which the request is made by entering the address directly in the browser. A Post is one in which a form has been posted back to the server. The Validate attribute tells ASP.NET to delay loading the request until an actual request is made against the handler, which improves site start time.

Before the PDF file is selected and served to the client, several checks are done to make sure the document request is valid, and the document actually exists. While it is not shown in this example, there could also be an authorization check to ensure that the user has rights to the file.

First, checking that the file requested is actually a PDF, check the file’s extension using the Path.GetExtension method. This method returns the file extension of the filename.

This example introduces the Response.TransmitFile method. This method transmits the file directly to the response stream without buffering the content. This means that there the file will be directly streamed to the client without building any sort of temporary memory buffer for the content.

The TransmitFile method is new to the 2.0 framework and replaces the WriteFile method. The main difference between the two methods is loading or not loading the file contents into the server’s memory. The TransmitFile method does not write the file to memory, which is ideal for very large files. The WriteFile method does write to memory, which can cause the ASP.NET worker process to restart during the process. There is an overloaded version of the WriteFile method that accepts a Boolean to indicate if the file should be buffered or not. For large files, this should be set to false.

In the example, you will see code lifted from a 2003 Scott Hanselman Blog, www.hanselman.com/blog/InternetExplorerAndTheMagicOfMicrosoftKBArticleQ293792.aspx. At the time, Internet Explorer 5 and below, as well as a few other older browsers, were demonstrating some odd behavior when downloading a PDF document. They seemed to be making the same request three times, and thus the server would return the document three times. This not only consumed unnecessary bandwidth, but also slowed down the user experience.

Scott points out some simple techniques that can be used to counter this problem by examining the Request headers and returning a more appropriate response. One checks the UserAgent or browser, and the other examines the client language.

While this issue seems to have been resolved with more modern browsers, it is a good practice to use this code in case an older browser is used by a client or the issue resurfaces again.

In this instance, the ContentType is set to “application/pdf.” This tells the browser that it is loading a PDF document and should act accordingly. The next line adds a custom header, Content-Disposition. This is an RFC (2183) specification on how content is presented to the user — inline or as an attachment. When inline is specified, the content is viewed directly, without any input required from the user. When attachments is specified, then some action is required by the user, such as specifying if the content should be saved or opened directly.

Return a Protected PDF Document

VB

Imports System

Imports System.Web

Imports System.IO

 

Public Class GetPDFs : Implements IHttpHandler

 

 

#Region " Properties "

 

    Private _context As HttpContext

    Public Property CurrentContext() As HttpContext

        Get

            Return _context

        End Get

        Set(ByVal value As HttpContext)

            _context = value

        End Set

    End Property

 

    Private _Response As HttpResponse

    Public Property Response() As HttpResponse

        Get

            Return _Response

        End Get

        Set(ByVal value As HttpResponse)

            _Response = value

        End Set

    End Property

#End Region

 

 

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

 

        If IsNothing(context) Then

            Throw New ArgumentNullException(             "There is no httpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException(             "There is no httpContext.Response Associated with the Request.")

        End If

 

        Dim sDocument As String = String.Empty

 

        If Path.GetExtension(context.Request.Url.ToString) = "pdf" Then

            context.Server.Transfer("badpdf.aspx")

        End If

 

        'First run the supplied filename through a URL decoder to change any URL

        'characters back to their normal selves.

        sDocument = HttpUtility.UrlDecode(_

            Path.GetFileName(context.Request.Url.ToString))

        'Now build a full physical path to the file on the server.

        sDocument = Path.Combine(Path.Combine(_

            context.Request.PhysicalApplicationPath, "PDF"), sDocument)

 

        'Verify we actually have the file to serve.

        If File.Exists(sDocument) = False Then

            context.Server.Transfer("badpdf.aspx")

        End If

 

        'The next two sections are from Scott Hanselman's blog,           but seem to be out of date,

        'since it was posted in 2003

        'http://www.hanselman.com/blog/

          InternetExplorerAndTheMagicOfMicrosoftKBArticleQ293792.aspx

        'This is done to support older browsers that were not as efficient

        'at managing document requests.

        If InStr(1, context.Request.UserAgent, "contype") > 0 Then

            'Just send the mime/type

            Response.ContentType = "application/pdf"

            Response.End()

            Exit Sub

        End If

 

        Dim Language As String = context.Request.ServerVariables("HTTP_ACCEPT_LANGUAGE")

        If String.IsNullOrEmpty(Language) Then

            Response.Clear()

            Response.ContentType = "application/pdf"

            Response.AddHeader("Last-modified", "Mon, 01 Sep 1997 01:03:33 GMT")

            Response.Status = "304 Not Modified"

            Response.End()

            Exit Sub

        End If

 

        'Set the Cacheability

        Response.Cache.SetCacheability(HttpCacheability.Public)

        Response.Cache.SetExpires(DateTime.MinValue)

        Response.ContentType = "application/pdf"

 

        'This opens the Open/Save Dialog

        Response.AddHeader("Content-Disposition", "attachment; filename=" & _

         Path.GetFileName(sDocument))

 

        'This Bypasses the Open/Save Dialog

        'Response.AddHeader("Content-Disposition", "inline; filename=" & _

        ' Path.GetFileName(sDocument))

 

        If File.Exists(sDocument) Then

            'Write the file to the Response Output stream without buffering.

            'This is new to ASP.NET 2.0. Prior to that use WriteFile.

            Response.TransmitFile(sDocument)

            Response.End()

        End If

 

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean Implements          IHttpHandler.IsReusable

        Get

            Return False

        End Get

    End Property

 

End Class

C#

using System;

using System.Web;

using System.IO;

 

public class GetPDFs : IHttpHandler

{

 

    public void ProcessRequest(HttpContext context)

    {

 

        string sDocument = string.Empty;

        string sURL = context.Request.Url.ToString();

 

        if (Path.GetExtension(sURL) == "pdf")

        {

            context.Server.Transfer("badpdf.aspx");

        }

 

        //First run the supplied filename through a URL decoder to change any URL

        //characters back to their normal selves.

        sDocument = HttpUtility.UrlDecode(Path.GetFileName(sURL));

        //Now build a full physical path to the file on the server.

        sDocument =

             Path.Combine(Path.Combine(context.Request.PhysicalApplicationPath,              "PDF"), sDocument);

 

        //Verify we actually have the file to serve.

        if (File.Exists(sDocument) == false)

        {

            context.Server.Transfer("badpdf.aspx");

        }

 

        //The next two sections are from Scott Hanselman's blog,            but seem to be out of date,

        //since it was posted in 2003

        //http://www.hanselman.com/blog/

           InternetExplorerAndTheMagicOfMicrosoftKBArticleQ293792.aspx

        //This is done to support older browsers that were not as efficient

        //at managing document requests.

        if (context.Request.UserAgent.Contains ("contype"))

        {

            //Just send the mime/type

            context.Response.ContentType = "application/pdf";

            context.Response.End();

            return; // TODO: might not be correct. Was : Exit Sub

        }

 

        string Language =

    context.Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"];

        if (string.IsNullOrEmpty(Language))

        {

            context.Response.Clear();

            context.Response.ContentType = "application/pdf";

            context.Response.AddHeader("Last-modified",

            "Mon, 01 Sep 1997 01:03:33 GMT");

            context.Response.Status = "304 Not Modified";

            context.Response.End();

            return; // TODO: might not be correct. Was : Exit Sub

        }

 

        context.Response.Cache.SetExpires(DateTime.MinValue);

        context.Response.ContentType = "application/pdf";

 

        if (File.Exists(sDocument))

        {

            //Write the file to the Response Output stream without buffering.

            //This is new to ASP.NET 2.0. Prior to that use WriteFile.

            context.Response.TransmitFile(sDocument);

            context.Response.End();

        }

 

    }

 

    public bool IsReusable

    {

        get { return false; }

    }

 

}

Publish a Stock Quote

So far all the examples are synchronous handlers, or handlers that process the request in one continuous process. Sometimes a handler builds the content of a request in a long-running process. A common example would be where content is collected from a third-party web service or source. An asynchronous handler provides a mechanism to have a callback method execute the long request on a thread that is returned to the available thread pool, while waiting for the process to return, without causing the client to wait for the full response.

If a user of a web site has to wait for an extended period of time for content to be rendered in the browser, that user will often assume that the request is hung, the site is broken, or he made a mistake. The user’s common response will be to either resend the request, causing more issues, or worse—leave your site for a competitor’s. This is why many sites that have long-running processes try to display some sort of animated graphic to indicate to the user that the site is working, and the user just needs to wait a bit. A common example is a travel site looking for potential hotels or flights in the reservation system.

The IHttpAsyncHandler interface inherits from IHttpHandler. It implements the ProcessRequest and the IsResuable methods but also defines two extra methods, BeginProcessRequest and EndProcessRequest. These methods make up the mechanism needed to handle asynchronous calls within ASP.NET.

The BeginProcessRequest method creates an instance of a delegate that is used to actually process the request. BeginProcessRequest receives a reference to the current context object an AsyncCallBack object and a generic Object parameter that contains any extra data passed by the request. The extraData parameter can be casted to any object that might be used in the actual processing of the request.

Retrieve a Stock Quote via an httpHandler

VB

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

 

Imports System

Imports System.IO

Imports System.Net

Imports System.Web

 

Public Delegate Sub ProcessRequestDelegate2(ByVal ctx As HttpContext)

 

Public Class PublishStockQuote : Implements IHttpAsyncHandler

 

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

 

        Dim si As StockInfo = ReadStockInfo("MSFT")

 

        context.Response.Write(String.Format(           "<P>MSFT Price: {0:c}</P>", si.LastPrice))

        context.Response.Write(String.Format(           "<P>MSFT Volume: {0} Shares</P>", si.Volume))

 

        context.Response.Flush()

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean Implements

IHttpHandler.IsReusable

        Get

            Return False

        End Get

    End Property

 

    Public Function BeginProcessRequest(ByVal context As System.Web.HttpContext, _

              ByVal cb As System.AsyncCallback, _

              ByVal extraData As Object) As System.IAsyncResult            Implements System.Web.IHttpAsyncHandler.BeginProcessRequest

 

 

           context.Response.Write("Beginning Request.....<BR/>")

           Dim prg As New ProcessRequestDelegate2(AddressOf ProcessRequest)

           Return prg.BeginInvoke(context, cb, extraData)

    End Function

 

    Public Sub EndProcessRequest(ByVal result As System.IAsyncResult) Implements System.Web.IHttpAsyncHandler.EndProcessRequest

 

    End Sub

 

    Private Function ReadStockInfo(ByVal StockSymbol As String) As StockInfo

 

        Dim s As String() = ReadURL(String.Format(_

            "http://finance.yahoo.com/d/quotes.csv?s={0}&f=sl1d1t1c1ohgv&e=.csv", _

            StockSymbol)).Split(",")

 

        Dim si As New StockInfo

 

        If IsNumeric(s(1)) Then

            si.LastPrice = s(1)

        End If

 

        If IsNumeric(s(8)) Then

            si.Volume = s(8)

        End If

 

        Return si

 

    End Function

 

    Private Function ReadURL(ByVal sURL As String) As String

 

        Dim client As New WebClient()

 

        ' Add a user agent header in case the

        ' requested URI contains a query.

        client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)")

 

        Dim data As Stream = client.OpenRead(sURL)

        Dim reader As New StreamReader(data)

        Dim s As String = reader.ReadToEnd()

 

        data.Close()

        reader.Close()

 

        Return s

 

    End Function

 

 

End Class

            

C#

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

 

using System;

using System.IO;

using System.Net;

using System.Web;

 

public delegate void ProcessRequestDelegate2(HttpContext ctx);

 

public class PublishStockQuote : IHttpAsyncHandler

{

 

    public void ProcessRequest(HttpContext context)

    {

 

        StockInfo si = ReadStockInfo("MSFT");

 

        context.Response.Write(string.Format("<P>MSFT Price: {0:c}</P>", si.LastPrice));

        context.Response.Write(string.Format("<P>MSFT Volume: {0} Shares</P>", si.Volume));

 

       // context.Response.Flush();

    }

 

    public bool IsReusable {

        get { return false; }

    }

 

    public System.IAsyncResult

        BeginProcessRequest(

            System.Web.HttpContext context, System.AsyncCallback cb, object extraData)

    {

 

        context.Response.Write("Beginning Request.....<BR/>");

 

        ProcessRequestDelegate2 prg = new ProcessRequestDelegate2(ProcessRequest);

        return prg.BeginInvoke(context, cb, extraData);

    }

 

    public void EndProcessRequest(System.IAsyncResult result)

    {

 

    }

 

    private StockInfo ReadStockInfo(string StockSymbol)

    {

 

        string[] s = ReadURL(

            string.Format("http://finance.yahoo.com/d/quotes.csv?s={0}&f=sl1d1t1c1o hgv&e=.csv",

            StockSymbol)).Split(',');

 

        StockInfo si = new StockInfo();

 

        if (IsNumeric(s[1])) {

            si.LastPrice = Convert.ToDouble(s[1]);

        }

 

        if (IsNumeric(s[8])) {

            si.Volume = Convert.ToInt32(s[8]);

        }

 

        return si;

 

    }

 

    //This is to keep things simple for the C# version

    internal static bool IsNumeric(object ObjectToTest)

    {

        if (ObjectToTest == null)

        {

            return false;

 

        }

        else

        {

            double OutValue;

            return double.TryParse(ObjectToTest.ToString().Trim(),

                System.Globalization.NumberStyles.Any,

 

                System.Globalization.CultureInfo.CurrentCulture,

 

                out OutValue);

        }

    }

 

 

    private string ReadURL(string sURL)

    {

 

        WebClient client = new WebClient();

 

        // Add a user agent header in case the

        // requested URI contains a query.

        client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0;         Windows NT 5.2; .NET CLR 1.0.3705;)");

 

        Stream data = client.OpenRead(sURL);

        StreamReader reader = new StreamReader(data);

        string s = reader.ReadToEnd();

 

        data.Close();

        reader.Close();

 

        return s;

 

    }

 

}

Return a Compressed Document

Another common need many businesses have is to dynamically compress documents as they are served. The .NET framework includes some basic classes to perform GZip and Deflate compression, which can be leveraged to compress a single document. They do have some limitations because they will only compress one file at a time and will not preserve the filename with the original extension. There are some enhanced third-party libraries that help with this, but for demonstration purposes, here I limit it to the framework’s compression classes.

The compression classes are located in the System.IO.Compression namespace, which should be included as a reference on the handler. Any namespace needed to perform the logic of the handler can be imported at the file level or globally in the web.config file. For this example, the handler needs the System.IO, System.IO.Compression, System, and System.Web namespaces to function.

Example NameSpace Imports

VB

Imports System

Imports System.Web

Imports System.IO.Compression

Imports System.IO

C#

using System;

using System.Web;

using System.IO.Compression;

using System.IO;

Just like the other examples, the context and response objects are verified along with any needed input parameters. Once these requirements have been met, the actual path to the request document is built, and its existence is verified. If it does not exist, the user is gracefully alerted to this problem. If the file actually exists, the path is passed to a method that will create the compressed archive on the fly and return it to the Response Stream.

This time the ContentType is set to “application/zip” to indicate to the browser that a zip file is being sent to the client. The Content-Disposition is set to an attachment to force the user to either open it or save it to disk. In this situation, it is a good idea to force input from the user because a zip file is a binary file that would not open gracefully in a browser. So an inline Content-Disposition is not really an option.

Finally, the Content-Length of the zip archive is added to the Response headers to let the browser know how much data to expect. To send the zip archive to the client, the Response.BinaryWrite method is used, which streams the bytes from the MemoryStream used to build the archive at run time.

Return a Compressed Document

VB

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

 

Imports System

Imports System.Web

Imports System.IO.Compression

Imports System.IO

 

Public Class ZipMe : Implements IHttpHandler

 

    Private Const buffer_size As Integer = 100

 

#Region " Properties "

 

    Private _context As HttpContext

    Public Property CurrentContext() As HttpContext

        Get

            Return _context

        End Get

        Set(ByVal value As HttpContext)

            _context = value

        End Set

    End Property

 

    Private _Response As HttpResponse

    Public Property Response() As HttpResponse

        Get

            Return _Response

        End Get

        Set(ByVal value As HttpResponse)

            _Response = value

        End Set

    End Property

 

#End Region

 

 

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

 

        If IsNothing(context) Then

            Throw New ArgumentNullException(             "There is no httpContext Associated with the Request.")

        End If

 

        If IsNothing(context.Response) Then

            Throw New ArgumentNullException(             "There is no httpContext.Response Associated with the Request.")

        End If

 

        If IsNothing(context.Request("doc")) Then

            Throw New ArgumentNullException(             "There is no Filename to commpress and retrieve.")

        End If

 

        CurrentContext = context

        Response = context.Response

 

        Dim sFileName As String = Path.GetFileName(context.Request("doc").ToString)

 

        If String.IsNullOrEmpty(sFileName) = False Then

 

            sFileName = Path.Combine(Path.Combine(_

                   CurrentContext.Request.PhysicalApplicationPath,                       "docs"), sFileName)

 

            If File.Exists(sFileName) Then

                GZipCompress(sFileName)

            Else

                WriteError("Sorry the requested file does not exist.")

            End If

 

        Else

            WriteError("Sorry you need to request a file.")

        End If

 

    End Sub

 

    Public ReadOnly Property IsReusable() As Boolean          Implements IHttpHandler.IsReusable

        Get

            Return True

        End Get

    End Property

 

    Private Sub WriteError(ByVal sError As String)

        Response.ClearContent()

        Response.ContentType = "text/HTML"

        Response.Write(String.Format("<h3>{0}</h3>", sError))

 

    End Sub

 

 

    Public Sub GZipCompress(ByVal filename As String)

 

        Dim infile As FileStream

        Try

 

            Dim sFileName As String = Path.GetFileName(filename)

 

            ' Open the file as a FileStream object.

            infile = New FileStream(filename,                      FileMode.Open, FileAccess.Read, FileShare.Read)

            Dim buffer(infile.Length - 1) As Byte

            ' Read the file to ensure it is readable.

            Dim count As Integer = infile.Read(buffer, 0, buffer.Length)

            If count <> buffer.Length Then

                infile.Close()

                WriteError("Sorry but the file you requested is empty.")

                Return

            End If

            infile.Close()

 

            ' Use the newly created memory stream for the compressed data.

            Dim ms As New MemoryStream()

 

            'This uses the .NET Built in GZipStream class to create a             zip file of the requested

            'document. One drawback is it will not retain the full filename in the             archive.

            'For a more complete version check out             http://msdn2.microsoft.com/en-us/magazine/cc163727.aspx.

            Dim compressedzipStream As New GZipStream                   (ms, CompressionMode.Compress, True)

            compressedzipStream.Write(buffer, 0, buffer.Length)

 

            ' Close the stream.

            compressedzipStream.Close()

 

            Response.ClearContent()

            Response.ContentType = "application/zip"

            Response.AddHeader("Content-Disposition", "attachment; filename=" & _

                     sFileName.Split(".")(0) & ".zip")

            Response.AddHeader("Content-Length", ms.Length.ToString())

            Response.BinaryWrite(ms.GetBuffer())

 

 

            ' ms.Close()

            ' Response.End()

 

        Catch e As Exception

            WriteError("Error: The file being read contains invalid data.")

        End Try

 

    End Sub 'GZipCompressDecompress

 

End Class

C#

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

 

 

using System;

using System.Web;

using System.IO.Compression;

using System.IO;

 

public class ZipMe : IHttpHandler

{

 

    private const int buffer_size = 100;

 

    #region " Properties "

 

    private HttpContext _context;

    public HttpContext CurrentContext {

        get { return _context; }

        set { _context = value; }

    }

 

    private HttpResponse _Response;

    public HttpResponse Response {

        get { return _Response; }

        set { _Response = value; }

    }

 

    #endregion

 

 

    public void ProcessRequest(HttpContext context)

    {

 

        if ((context == null)) {

            throw new ArgumentNullException(             "There is no httpContext Associated with the Request.");

        }

 

        if ((context.Response == null)) {

            throw new ArgumentNullException(             "There is no httpContext.Response Associated with the Request.");

        }

 

        if ((context.Request["doc"] == null)) {

            throw new ArgumentNullException(             "There is no Filename to commpress and retrieve.");

        }

 

        CurrentContext = context;

        Response = context.Response;

 

        string sFileName = Path.GetFileName(context.Request["doc"].ToString());

 

        if (string.IsNullOrEmpty(sFileName) == false) {

 

            sFileName = Path.Combine(

                  Path.Combine(                   CurrentContext.Request.PhysicalApplicationPath,                   "docs"), sFileName);

 

            if (File.Exists(sFileName)) {

                GZipCompress(sFileName);

            }

            else {

                WriteError("Sorry the requested file does not exist.");

            }

        }

 

        else {

            WriteError("Sorry you need to request a file.");

        }

 

    }

 

    public bool IsReusable {

        get { return false; }

    }

 

    private void WriteError(string sError)

    {

        Response.ClearContent();

        Response.ContentType = "text/HTML";

        Response.Write(string.Format("<h3>{0}</h3>", sError));

    }

 

    public void GZipCompress(string filename)

    {

 

        FileStream infile;

        try {

 

            string sFileName = Path.GetFileName(filename);

 

            // Open the file as a FileStream object.

            infile = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);

            byte[] buffer = new byte[infile.Length];

            // Read the file to ensure it is readable.

            int count = infile.Read(buffer, 0, buffer.Length);

            if (count != buffer.Length) {

                infile.Close();

                WriteError("Sorry but the file you requested is empty.");

                return;

            }

            infile.Close();

 

            // Use the newly created memory stream for the compressed data.

            MemoryStream ms = new MemoryStream();

 

            //This uses the .NET Built in GZipStream class to create a             zip file of the requested

            //document. One drawback is it will not retain the full filename in the

            archive.

            //For a more complete version check out              http://msdn2.microsoft.com/en-us/magazine/cc163727.aspx.

            GZipStream compressedzipStream =                   new GZipStream(ms, CompressionMode.Compress, true);

            compressedzipStream.Write(buffer, 0, buffer.Length);

 

            // Close the stream.

            compressedzipStream.Close();

 

            Response.ClearContent();

            Response.ContentType = "application/zip";

            Response.AddHeader("Content-Disposition", "attachment; filename=" +

                    sFileName.Split('.')[0] + ".zip");

            Response.AddHeader("Content-Length", ms.Length.ToString());

            Response.BinaryWrite(ms.GetBuffer());

        }

 

 

        // ms.Close()

        // Response.End()

 

        catch (Exception e) {

            WriteError("Error: The file being read contains invalid data.");

        }

 

    }

    //GZipCompressDecompress

 

}

If there is an exception in creating the zip archive, the user is alerted with a nice Error Handling message. A common method was written to handle these error reports, to keep them consistent.

Graceful Error Writing Method

VB

Private Sub WriteError(ByVal sError As String)

    Response.ClearContent()

    Response.ContentType = "text/HTML"

    Response.Write(String.Format("<h3>{0}</h3>", sError))

 

End Sub

C#

private void WriteError(string sError)

{

    Response.ClearContent();

    Response.ContentType = "text/HTML";

    Response.Write(string.Format("<h3>{0}</h3>", sError));

}

Summary

Custom httpHandlers are a great way to build content to send to the browser dynamically. A custom handler is the best way to have complete control over the actual content being sent to the browser, which means you can control how optimized the content is that is being sent to the browser. Handlers can be used to stream HTML, images, documents, and other dynamic binary content to the client.

The introduction of IIS 7 now makes the use of custom httpHandlers even more important to building highly optimized responses through the integrated pipeline, and for the ability for server-level control of content being served. Of course, previous versions of ASP.NET and IIS allow custom httpHandlers to build optimized responses too. Remember, the foundation of ASP.NET is built using custom handlers that create Web Forms, user controls, and more. You can take advantage of this and control so much by using httpHandlers.

About Chris Love

Chris Love has more than 14 years of experience in software design, development, and architecture. He has been the principal developer for more than 250 small and medium ASP and ASP.NET web sites over the past 7 years. These projects have exposed Chris to a wide range of Microsoft-related technologies to solve real business problems for his clients. He has learned to look objectively at his clients’ businesses and offer pragmatic suggestions to make them more efficient and profitable.

He has focused primarily on ASP.NET and VB.NET to produce the majority of his web sites. SQL Server is his database of choice, but he has valuable experience with Microsoft Access and interfacing to 3270 Main Frames as well.

Other responsibilities have included managing and maintaining production Windows servers for the Web, database, and e-mail. He has had to learn to manage performance, security, and spam safeguards to keep his customers operating 24/7.

Chris’s clients have also relied on his experience and expertise to help develop online marketing strategies, including search engine optimization and pay-per-click campaigns. Chris has begun to leverage this experience along with his ASP.NET expertise to build his own web properties and practice his technical and marketing theories first-hand.

In January 2008, Chris received the Microsoft MVP award in ASP.NET because of his expertise and community involvement. Chris has been active in a leadership role in the local user’s group TRINUG for more than 5 years. He served as the Vice President for the first 3 years and is currently on the advisory board. He has also led monthly a Special Interest Group (SIG), where he covers practical ASP.NET programming techniques, including how to use httpHandlers, httpModules, extending the .NET framework, and using third-party tools. He frequently presents and organizes Code Camps around the country. He has recently completed his first book, ASP.NET 2.0: Your Visual Blueprint for Developing Web Applications (Wiley Publishing Inc., 2007).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.199.184