4me

If the planet was going to break up–all right. But while I was alive I would go on living at the best speed I could manage. – “Future Imperfect”

Implementing HTTP Handlers in ASP.NET

Posted by tcle on October 21, 2008

Notes from Michael Flanakin

When I first started out, it was hard to find information on how to setup an ASP.NET HTTP handler. Of course, after time, I’ve found a wealth of articles, posts, and comments from others on these and other related topics. As my second post in the HTTP module/handler saga, I hope to give you an in-depth discussion on the topic of handlers to include pros, cons, and a sample implementation that you can extend.

Introduction

ASP.NET uses HTTP handlers to process all requests. For general information on HTTP handlers (and modules), see my previous article, Introduction to ASP.NET HTTP Modules and Handlers. In the following sections, I will discuss a few pros and cons of HTTP handlers and provide a step-by-step guide to implementing your own handler.

Most notably, HTTP handlers are beneficial because they provide a way to interact with HTTP requests before they get to a web page. This can be very nice, depending on what you need to do with the request. For instance, perhaps there is a need for logging actions taken by users or controlling access to individual files (i.e. images, executables). For the purposes of this article, I will discuss using handlers for URL rewriting.

This is intended to be a work in progress, so let me know if there is something extra that you’d like to see in it, or, if there are any mistakes/inconsistencies. Any other feedback is welcome, as well.

Creating the Handler

One of the most important things to implementing your HTTP handler is the management of your URL mappings. Before you look at how the handler should be coded, you should put some thought into how flexible you want the mappings to be. There are countless methods for managing your mappings, each with its own set of pros and cons. For instance, you could technically put them in a database; which would allow you to setup a nice front-end to manage them from within your application. The problem with this is that you’ll require a database call simply to find out what page you want to access. This may or may not be adequate. I would assume that the latter would be true in most situations. You should also consider the fact that, in some cases, you may require more than one rewrite or redirect in order to setup your mappings appropriately. For this article, I will keep it very simplistic. We will use the custom app settings section available within the Web.config file. To do this, add the following section to your Web.config file:

Web.config appSettings Configuration

< appSettings >
<add key="/MyApp/LogicalPage1.aspx" value="~/Pages/PhysicalPage1.aspx" />
<add key="/MyApp/LogicalPage2.aspx" value="~/Pages/PhysicalPage2.aspx" />
</appSettings>

The key is intended to be the requested page and the value is the physical page that will be displayed. Pretty simple. Two important things to note are that, using this simplified scenario, the keymust be and the value should be root-relative paths.. For instance, the above specifies that http://localhost/MyApp/LogicalPage1.aspx will actually map to http://localhost/MyApp/Pages/PhysicalPage1.aspx.

Now that we’ve defined our mappings, I recommend that you create a configuration settings reader to load and act upon the appropriate mapping at runtime. For this example, I won’t get into that, though. This simple implementation only requires a one-line lookup, so there is not much of a need to have the settings reader; however, in a real-world app, I would highly suggest using one for extensibility reasons. I will discuss this more in-depth later.

Creating the HTTP Handler

Now that we have decided on our mapping storage method and have ensured a way to read the mappings (built-in configuration support for now), all we have to do is create the HTTP handler. There are a lot of different ways to do this, so the first thing to think about is: What do you want to do? For this article, we’re just rewriting the URL, but for your system, you might want to add application-level logic. If this is the case, I recommend that you create special business objects to handle each logical task that needs to be accomplished. For instance, a LogAction class for logging or a RewriteUrl class for the URL rewriting. Since we will only be implementing a simple URL rewrite, I won’t bother getting into the complexities of a separate class.

Before you set forth with creating your HTTP handler, you should take a look at the IHttpHandler interface, which you will need to implement.

IHttpHandler Interface

public interface IHttpHandler
{
bool IsReusable { get; }
void ProcessRequest(HttpContext context);
}

There is one property and one method to implement. The property, IsReusable, specifies whether ASP.NET should reuse the same instance of the HTTP handler for multiple requests. My thinking is that, unless there is a specific reason not to, you would always want to reuse the HTTP handler. Unfortunately, I haven’t found any guidance suggesting one way or another – at least, not with any real reasoning behind it. The only thing I found was something to the effect of, unless your handler has an expensive instantiation, set IsReusable to false.

The ProcessRequest() method is where you will actually perform the logic to handle the request. Since we’re simply reading from the app settings and rewriting the URL, we can handle this in a matter of lines.

HttpHandler.ProcessRequest() Method

public void ProcessRequest(HttpContext context)
{
// declare vars
string requestedUrl;
string targetUrl;
int urlLength;

// save requested, target url
requestedUrl = context.Request.RawUrl;
if ( requestedUrl.IndexOf(“?”) >= 0 )
targetUrl = ConfigurationSettings.AppSettings[requestedUrl.Substring(0, requestedUrl.IndexOf(“?”))];
else
targetUrl = ConfigurationSettings.AppSettings[requestedUrl];
if ( targetUrl == null || targetUrl.Length == 0 )
targetUrl = requestedUrl;

// save target url length
urlLength = targetUrl.IndexOf(“?”);
if ( urlLength == -1 )
urlLength = targetUrl.Length;

// rewrite path
context.RewritePath(targetUrl);
IHttpHandler handler = PageParser.GetCompiledPageInstance(
targetUrl.Substring(0, urlLength), null, context );
handler.ProcessRequest(context);
}

Now, all we need to do is add the HTTP handler reference in the Web.config file. A lot of people have been falling victim to the following Server.Transfer() error because of incorrect handler configurations, so pay attention to this part.

Error executing child request for [physical page specified in appSettings value].aspx

I’ll discuss the reasoning behind the following configuration, but for now, simply replace "*/Pages/*.aspx" with an appropriate path that represents all of the physicalpages (this is veryimportant), MyApp.HttpHandler with the fully-qualified class path of the HTTP handler, and MyApp with the name of the assembly, minus the .dll extension. Also note that the handler for the physical pages must come first. These handlers are checked in order, so if you put it second, then the first path that the request matches will be used, which will probably be your custom handler.

Web.config system.web/httpHandlers Configuration

<system.web>
<httpHandlers>
<add
verb="*"
path="*/Pages/*.aspx"
type="System.Web.UI.PageHandlerFactory" />
<add
verb="*"
path="*.aspx"
type="MyApp.HttpHandler,MyApp" />
</httpHandlers>
</system.web>

Handling PostBack

Now that we have our URL rewriting in place, it’s time to do some real work. Based on this section’s title, you’ve probably figured out that you’re going to have some post-back issues (if you haven’t already tested that out). The problem with post-back is that, when rendered, the HtmlForm object sets the action to the physical page name. Of course, this means that when you submit the form, your true page is displayed. This is obviously less than ideal for URL beautification. Not to mention it would most likely confuse your users. Well, there are two solutions to consider.

First, you can add a simple script block to fix the problem. This is the easiest solution, but there’s one problem: if a user has scripting turned off (as if that is ever the case, anyway), the fix will be nullified. But, in case you still like this solution (I do), add this code to your Page class. If you don’t already, I’d suggest creating a base Page object for all of your pages to implement. Then, add this code to the base Page class. This allows you a good deal of extensibility as far as adding common features easily.

Register Javascript

RegisterStartupScript( "PostBackFix",
"<script>document.forms[0].action='';</script>" );

Your second option is to extend the HtmlForm class. This is pretty simple, as you will see below, but it comes with its own issues. The main problem that I have with this solution is that you have to explicitly add the extended HtmlForm object to replace the default HTML form tag. Not that it is hard to do, but it can get tedious if you’re creating (or converting) a lot of pages.

Action-less HtmlForm Object

public class ActionlessForm : HtmlForm
{
protected override void RenderAttributes(HtmlTextWriter writer)
{
Attributes.Add("enctype", Enctype);
Attributes.Add("id", ClientID);
Attributes.Add("method", Method);
Attributes.Add("name", Name);
Attributes.Add("target", Target);
Attributes.Render(writer);
}
}

Each method has it’s own pros and cons. They’re pretty simple to understand, so the decision shouldn’t be too hard. Honestly, you can implement the second option through a base Page class, but that adds a lot more complexity to your system then you’re probably looking for. Explore your options and be innovative.

Redirect, Transfer or Rewrite?

Earlier, we implemented a URL rewriting scheme; however, in some circumstances, you may wish to implement a Response.Redirect() or Server.Transfer() instead. One reason to do this is to forward from Default.aspx to another page, like Home.aspx. You may or may not want to do this “behind the scenes,” but that is a decision for you to make yourself. As always, each option comes with its own set of pros and cons.

Redirects are essentially two GET (or POST) requests. The first request gets processed and then the Response.Redirect() sends a response back to the client with an HTTP 302 redirect command. This obviously causes a slight delay as the second request gets processed. Also, since the new URL is sent to the client, it won’t be hidden. This clearly doesn’t support URL beautification, which is the main reason most people implement handlers. Even though redirects won’t solve your problems, they still play an important part in the overall solution and should be considered when developing your mapping solution.

Transfers, unlike redirects, keep control within the application; but, they are still treated as two requests. The difference is that instead of the client handling the HTTP 302 redirect command, the web server handles it. This means that any modules, as well as the handler, will be processed twice . There are three key things to remember when using transfers: (1) the Request object, and all of its properties and methods, will reflect the initial request (logical page) and not the physical page; (2) post-back will not work; and, (3) in order to use the transfer you have to have two handlers specified in the Web.config file. There might be a way to get the post-back to work, but I don’t know what that would entail. Perhaps I will delve into the ASP.NET request process fully one day. As for the two handler issue, let me explain that in a bit more detail. As you may remember from above, you specified two handlers in the Web.config file. The reason for this is because after the Server.Transfer() is executed, ASP.NET will send the second request back through the handler. I’m not completely sure why this is a problem, but it is. So, to fix it, you need to have some way to identify what requests should be handled by ASP.NET’s default handler and which should be handled by yours. I attacked this by putting all of my physical pages in a Pages directory. So, by re-adding the default handler to handle all requests to "*/Pages/*.aspx", we tell ASP.NET how to support each type of request. As I also mentioned before, this will fix the the “Error executing child request” error.

Rewrites provide the best performance because there is no back-tracking to re-handle requests. You simply change the URL and continue on with the request processing. Know that accessing the Request object will now reflect the new (physical) URL and you will not have access to the old (logical) URL. You can get around this by adding custom variables to the HttpContext, but that shouldn’t be necessary for most situations.

To add support for redirects and transfers, we can simply change our Web.config file by prepending “redirect.”, “transfer.”, or “rewrite.” to identify how we want the request handled. Then, update the IHttpHandler.ProcessRequest() method to treat them accordingly.

HttpHandler.ProcessRequest() Method

public void ProcessRequest(HttpContext context)
{
// declare vars
string requestedUrl;
string targetUrl;
int urlLength;

// save requested, target url
requestedUrl = context.Request.RawUrl;
if ( requestedUrl.IndexOf(“?”) >= 0 )
targetUrl = ConfigurationSettings.AppSettings[requestedUrl.Substring(0, requestedUrl.IndexOf(“?”))];
else
targetUrl = ConfigurationSettings.AppSettings[requestedUrl];

if ( targetUrl == null || targetUrl.Length == 0 )
targetUrl = requestedUrl;

// handle type
if ( targetUrl.StartsWith(“redirect.”) )
{
context.Response.Redirect(targetUrl.Substring(9));
}
else if ( targetUrl.StartsWith(“transfer.”) )
{
context.Server.Transfer(targetUrl.Substring(9));
}
else
{
// if type is specified, remove it
if ( targetUrl.StartsWith(“rewrite.”) )
targetUrl = targetUrl.Substring(8);

// save target url length
urlLength = targetUrl.IndexOf(“?”);
if ( urlLength == -1 )
urlLength = targetUrl.Length;

// rewrite path
context.RewritePath(targetUrl);
IHttpHandler handler = PageParser.GetCompiledPageInstance(
targetUrl.Substring(0, urlLength), null, context );
handler.ProcessRequest(context);
}

}

Conclusion

Well, congratulations on your first HTTP handler implementation. There is plenty of room for improvement, so try to think of how you can manage the mappings to add more than just a simple URL rewriting scheme. One thing that you might want to consider is a post-back processing component. Yes, post-back is handled by ASP.NET, but performance can be increased by removing that overhead. Anyway, my point is that there are a lot of things you can do to improve this simple implementation. I encourage you to add to this and let me know how well it works out for you. I’d be interested to hear some of the things people are doing with handlers. Good luck!

Special thanks to By Michael Flanakin,

This article was published on 26 Dec 2004 (http://www.developerfusion.com/article/4643/implementing-http-handlers-in-aspnet/1/ )

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: