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 key
must 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>
Comments