Adsence750x90

Monday, March 31, 2008

Debugging an HTTP Handler (*.ashx) in ASP.NET 2.0

A handler is responsible for fulfilling requests from a browser. Requests that a browser manages are either handled by file extension (or lack thereof) or by calling the handler directly. Only one handler can be called per request. A handler does not have any HTML static text like .aspx or .ascx files. A handler is a class that implements the IHttpHandler interface. If you need to interact with any Session information, you will also need to implement IRequiresSessionState. If you want to make an asynchronus handler, you will need to implement the IHttpAsyncHandler interface instead of the IHttpHandler interface.

Calling a Handler by File Extension

A handler can be invoked by any file extension that is mapped via the web.config and the IIS file extension mappings. You can have a file named something.billybob where 'billybob' is the file extension, or you can have no file extension at all such as http://web/handler/file where a request for 'file' without an extension invokes a handler mapped to *.* or the directory as *.

Calling a Handler Directory

The code file for the handler has the file extension of 'ashx' on the web server. This file can be called directly via a browser without having to set up web.config or IIS file extension mappings. For example: http://web/handler/handler.ashx. Some examples of this hander type are photo albums, RSS feeds, and blogging sites. Each of these is a good example of work that can be better accomplished without standard HTML. A photo album involves directory crawling and responding with pictures. A RSS feed returns information in the correct format.

Trace.axd as an Example Handler

An example of a handler that is invoked by file extension is the Trace.axd file used for debugging. In order to invoke the Trace.axd handler, you configure the website for tracing by adding a trace section to web.config:







and call the trace.axd file from the root of the website such as http://localhost/trace.axd.

Required IIS Mappings for Handlers Called by File Extension

Handlers that are invoked based on a request's file extension require configuration in web.config and in the IIS file extension mapping. In order to see the file mapping of the *.axd file, open the IIS Manager and configure the application. Go to the 'Mappings' tab and scroll down until you see the .axd file extension.

Double-click on the .axd extension in order to see more details about this mapping.

Invoking handlers by file extension or no extension gives you flexibility about what is called and when. There are many articles on the web about file extension handlers so this article will focus on the handler that is called directly.

Creating a Handler

In order to create a handler code file, you need a web site in Visual Studio 2005 (or Visual Studio 2003 for a .NET 1.x handler). For a new website, select 'ASP.NET Web Site'.


This will create a web site with an App_Data directory and the default.aspx file. You won't use the default.aspx so you can either leave it alone or delete it.

You will need to create the web.config and the handler file by clicking on the website in the Solution Explorer and add new items of 'web.config' and 'Generic Handler'.




The handler code file will look like:

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

using System;
using System.Web;

public class Handler : IHttpHandler {

public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}

public bool IsReusable {
get {
return false;
}
}

}

At this point, build the project and set the handler.ashx file as the startup file. Start debugging. You will be prompted to allow the process to change your web.config to allow debug. Unless you want to type in the change to allow debugging yourself (""), accept this change.

In my brower, because my desktop is set-up to parse text/plain as XML, I get an error that looks like:

The handler.ashx that Visual Studio provided needs to be fixed before it will work. So change the Content Type from 'text/plain' to 'text/html'. Build, and debug again. You should get a response that looks like:

Handler.ashx

The handler.ashx file implements the IHttpHandler interface and has two methods of 'ProcessRequest' and 'IsReusable'. The 'ProcessRequest' is your main method where you put your code. The 'IsReusable' method is set to true by default and indicates whether another request can use the IHttpHandler instance. If the instance can be reused mark it true -- this will improve the speed of the handler, and reduce the work your server will have to do. But if your instance should not be reused, due to state or because it is ansychronous, you should set IsReusable to false.

Session State

Before continuing, change the handler.ashx code to implement the IRequiresSessionState interface. If you need read-only access to the Session, implement the IReadOnlySessionState interface. Both interfaces are in the System.Web.SessionState namespace which you will need to add with the 'using' syntax. The code should now look like:

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

using System;
using System.Web;
using System.Web.SessionState;

public class Handler : IHttpHandler , IReadOnlySessionState{

public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
}

public bool IsReusable {
get {
return false;
}
}

}

Set a break point for the first line in the class and start debugging again.

Handling Issues with a Debug Build

When you use a debug build, you have the full resources of the Visual Studio debugger, all .NET Framework namespaces, and any system utilities and applications. The Visual Studio Debugger allows you to view the call stack, local variables, any watch variables, etc. You are probably familiar with using these features to some degree. However, with a HTTP Hander, debugging is much more important because the only visual indication of a bug is whatever you code into the content.

Handling Issues with a Release Build

The release build is different from the debug build in both features and performance. When you are using a release build, it is probably in a production environment. As such, you will need to use a different set of tools to find your issues.

Debugging with System.Diagnostics Namespace

The Diagnostics namespace has many classes to help with debugging in release and debug mode. If you are debugging in release mode, you should use the Trace class. If you are debugging in debug mode, you should use the Assert class. This article doesn't cover all the abilities of this namespace so you should investigate what class will help you solve your immediate problem.

In order to show these classes in action, change the code to use the namespace and use an infinite loop. The assertion is throw when the expression in the assertion is false, so the assertion will throw when the counter is no longer less than 10.

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

using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;

public class Handler : IHttpHandler , IReadOnlySessionState{

public void ProcessRequest (HttpContext context) {

context.Response.ContentType = "text/html";
context.Response.Write("Hello World");

int i=0;

while (1!=0)
{
Debug.Assert(i<10);>

Build and debug. Since there are no breakpoints set, the debugger will become active again when the assertion is hit. This is great if you know what the error is you are getting and can test for it. The first thing that happens is an assertion window pops up with information about the assertion. If you want the debugger to become active, click the "Retry" button. This will bring up the debugger at that assertion code line.

If you know a general code location you would like to look at instead of a specific Boolean statement to test for, you should use the Debugger.Break statement in your code. This will pop up the debugger as well. If you want to look at preconditions and then stop at the error, you can combine these two statements to quicken your debugging process. An example of that code is below:

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

using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;

public class Handler : IHttpHandler , IReadOnlySessionState{

public void ProcessRequest (HttpContext context) {

context.Response.ContentType = "text/html";
context.Response.Write("Hello World");

Debugger.Break();

int i=0;

while (1!=0)
{
Debug.Assert(i<10);>

You can use the Debug.Write, Debug.WriteIf, Debug.WriteLine, and Debug.WriteLineIf to print to the Output window during a debug session. This could help you view the state of the HTTP Handler.

If you want to track down an issue in a release build, you will have to have a way of peering into the code. One way to do that is to add EventLog.WriteEntry statements into your code. This allows you to add information to the event log as the HTTP Hander is running. The WriteEntry method is heavily overloaded so you will need to determine the best method for your use. If you want to see information as a simple information entry in the event viewer, you just use one String argument. The Event Viewer needs to know the "Source" of the event so let's name it "HTTPHandler - DebugArticle". This should be easy to see in the Event Viewer. The code looks like:

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

using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;

public class Handler : IHttpHandler , IReadOnlySessionState{

public void ProcessRequest (HttpContext context) {

context.Response.ContentType = "text/html";
context.Response.Write("Hello World");

int i=0;

// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "HTTPHandler-DebugArticle";

while (i<100) 10="=">

Build a release version of the handler and call it. The output should only include the "Hello World" text. Open the Event Viewer, Application log. You should see several postings from the source of "HTTP Handler - DebugArticle".

If you open one of the events, you should see a simple output:

There are also classes to interact with the Performance monitor, and processes as well as several classes to help with logic flow.

Attaching to a Process to Debug a Running HTTP Handler

Another method of finding your issues is to attach to a process that contains the HTTP Handler. If you are running multiple websites where each website is in it's own process space, you may have to figure out the process id. The easiest way is to have your handler tell you. The System.Diagnostics namespace includes a class called Process which will help you determine the current process's id. In order to find the id, let's send the id to the event viewer by changing the code to:

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

using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;

public class Handler : IHttpHandler , IReadOnlySessionState{

public void ProcessRequest (HttpContext context) {

context.Response.ContentType = "text/html";
context.Response.Write("Hello World");

int i=0;

Process.GetCurrentProcess().Id.ToString();

// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "HTTPHandler-DebugArticle";
myLog.WriteEntry("Process Id is " + Process.GetCurrentProcess().Id.ToString());

while (i<100) 10="=">

The important code line here is in the loop and is an alteration to the string in the myLog.WriteEntry:Process.GetCurrentProcess().Id. The following figure shows what the Event Viewer entry looks like:

Now that you know the process id, you need to find this id in the Task Manager. In order to see process ids in the Task Manager, click to the Process tab then click on View and choose Select Columns. Click on PID (Process Identifier) and OK out of the Dialogs. The list of processes should now include the PID, which you can sort by if you click on the column name. Once you find the right process id in the list, right click on it and choose Debug. This will start Visual Studio. You will have to open your code file from Visual Studio and debug at that point.





source

1 comment:

Anonymous said...

Do you have this code example in Visual basic.net