October 2008 - Posts

ASP.NET AJAX and HTTP Handlers: A Cautionary Tale
Monday, October 06, 2008 1:07 PM

For those of you using the ASP.NET AJAX Controls, there is a potential pitfall in how HTTP handlers are managed that can affect your AJAX applications.  This issue may take a little bit longer to explain than most, but that’s because it involves a concept probably unfamiliar to most AJAX developers:  the concept of HTTP handlers.

For background, HTTP handlers are intimately tied to the way in which ASP.NET handles requests on the server.  In one sense, you can think of them as the way in which ASP.NET makes use of a file’s extension – if a file ends in .aspx, for example, requests for it get will normally be processed by a class called System.Web.UI.PageHandlerFactory, which implements an interface called IHttpHandlerFactory.  The individual HTTP handlers created by an IHttpHandlerFactory all implement a method called ProcessRequest which does the heavy lifting of writing actual HTML to the response stream.  For .aspx pages, this is a relatively complicated process; other HTTP handlers can be as simple as generating Forbidden error messages when requesting files that shouldn’t be served to end-users (like, say, web.config). 

Before I can describe the scenario to watch out for, there are two other details about HTTP handlers to understand:  there is an extensibility model that lets you define your own handlers and handler factories, and you can control handler mappings yourself using the httpHandlers section in web.config.

Let’s say you have a nested hierarchy of virtual directories; to simplify things let’s say that they’re called /Main and /Main/SubApp.  They could be configured as application directories in IIS, but this isn’t actually necessary for the scenario I’m talking about.  Let’s furthermore say that you’ve defined your own set of HTTP handlers in the /Main application’s web.config file.  Part of it looks like this:

        <httpHandlers>

                       

            <add path="trace.axd" verb="*" type="System.Web.Handlers.TraceHandler" validate="True" />

            <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" />            <add path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" />                    </httpHandlers>

So far, everything’s okay.  When a request comes in to this application that ends in .axd, the HTTP handler that gets used is determined by which of the entries listed above is the first to match the incoming request.   That means that trace.axd and WebResource.axd requests get handled by their respective HTTP handlers, and everything else that ends in .axd gets served a 404 error. 

However, what happens when you later add a subdirectory whose pages contains AJAX controls?  If you created them using Visual Studio, you probably got an additional HTTP handler defined automatically in a new web.config file.  That addition will look something like:

              <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"/>              </httpHandlers>

In the above, ScriptResourceHandler.axd is the resource handler URL used to serve all of the javascript files found in the Microsoft AJAX Library.  So, what happens when you request a page that has a ScriptManager on it when it’s located in /Main/SubApp in the scenario I’ve outlined here?   Will the script requests for the AJAX Library scripts be served by the same ScriptResourceHandler it usually gets served by, or will it be served by something else?

The answer in this case is that it gets served not by ScriptResourceHandler, but rather by HttpNotFoundHandler.  The reason is that the order of httpHandler entries is significant, and when there are multiple web.configs (as there are here) the entries in the top web.config are understood to come first. Because ScriptResource.axd matches the *.axd pattern defined in the web.config file in /Main, HttpNotFoundHandler handles the request.  This typically causes a client-side javascript error with the message “'Sys' is undefined.”

If you want to avoid this, the simple thing to do is to make sure when rewriting the order of HTTP handlers, that the HTTP handler for ScriptResource.axd is defined before any handler that maps *.axd.  In this case, that’s just a one-line change to the web.config in /Main:

        <httpHandlers>

                        <add path="trace.axd" verb="*" type="System.Web.Handlers.TraceHandler" validate="True" />            <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" />            <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 path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" />                    </httpHandlers>

 

Clay Compton
ASP.NET QA Team

More Posts