Scott Hanselman

Conditionally Serve Files Statically or Dynamically with HttpHandlers

February 1, '07 Comments [3] Posted in ASP.NET | HttpHandler
Sponsored By

I have some static files - RSS XML in fact - that have been served by IIS as static files for a while. I consider them permalinks, and I don't want to change that for now, but I wanted to do some testing with FeedBurner (similar do what I did before with FeedBurner in DasBlog) letting them serve the RSS for a while, gathering statistics. If it went well, I'd go with FeedBurner full time for those files - but not all.

So, given a directory with: foo.xml, bar.xml, bat.xml and others all served statically by IIS, I wanted a quick and dirty way to maintain the permalinks while handling a few of the files dynamically, and a few as redirects to FeedBurner.

There's a number of ways to solved this depending on your version of IIS and ASP.NET; here's an easy way, and the way I'm doing it now.

By default IIS will serve XML files as static files with the text/xml mime type and ASP.NET will never hear about it. As XML files aren't handled by ASP.NET, first I need to configure IIS to pass all requests for XML files for this VDIR to ASP.NET. Do this from the Configuration button under Home Directory in the Properties pages of the app/vdir. I've pointed ".xml" to aspnet_isapi.dll as seen in the figure at right.

Note that you need to be explicit if you want IIS to check if the file exists. For example, if foo.xml is a real static file on disk, but bar.xml is going to be dynamically created, don't select "Verify that file exists." Setting this checkbox wrong is the #1 developer-error that you'll hit when creating HttpHandlers to handle custom extensions.

Next I'll add a handler in web.config for *.xml (or just foo.xml, etc, if I want to be more restrictive).

   1:  <httpHandlers>
   2:  <add verb="GET" 
   3:  path="*.xml"
   4:  type="Foo.Redirector,FooAssembly"
   5:  />
   6:  </httpHandlers>

Now I'll create a VERY simple HttpHandler that will fire for all .xml files (or just the ones you want, if you only associated a few files in your web.config.

This is your chance to:

  • Redirect elsewhere
  • Dynamically generate the file they asked for
  • Pick up the file off disk and serve it statically

The example below checks to see if the UserAgent requesting the files foo.xml or bar.xml is FeedBurner. (You could check other things of course, like IP subnet, etc.) If it isn't FeedBurner's bot, we currently redirect them temporarily with an HTTP 302. There's commented code for a permanent redirect, which RSS Readers respect, that would cause the client to update their bookmarks and never return. For now, I'll do a temporary one.

   1:  using System;
   2:   
   3:  namespace Foo
   4:  {
   5:      public class  Redirector : System.Web.IHttpHandler
   6:      {
   7:          void System.Web.IHttpHandler.ProcessRequest(System.Web.HttpContext context)
   8:          {
   9:              string userAgent = context.Request.UserAgent;
  10:              if (userAgent != null && userAgent.Length > 0)
  11:              {
  12:                  // If they aren't FeedBurner (optional example check)
  13:                  if (userAgent.StartsWith("FeedBurner") == false)
  14:                  {
  15:                      string redirect = String.Empty;
  16:                      string physicalpath = System.IO.Path.GetFileName( 
context.Request.PhysicalPath).ToUpperInvariant();
  17:                      switch (physicalpath)
  18:                      {
  19:                          case "BAR.XML":
  20:                              redirect = "http://feeds.feedburner.com/Bar";
  21:                              break;
  22:                          case "FOO.XML":
  23:                              redirect = "http://feeds.feedburner.com/Foo";
  24:                              break;
  25:                      }
  26:                      if (redirect != String.Empty)
  27:                      {
  28:                          context.Response.Redirect(redirect); //temporary redirect
  29:   
  30:                          //context.Response.StatusCode = 
(int) System.Net.HttpStatusCode.MovedPermanently; //permanent HTTP 301
  31:                          //context.Response.Status = "301 Moved Permanently";
  32:                          //context.Response.RedirectLocation = redirect;
  33:                          return;
  34:                      }
  35:                  }
  36:              }
  37:              context.Response.ContentType = "application/xml";
  38:              context.Response.TransmitFile(context.Request.PhysicalPath);
  39:          }
  40:   
  41:          bool System.Web.IHttpHandler.IsReusable
  42:          {
  43:              get { return true; }
  44:          }
  45:      }
  46:  }

If they ARE FeedBurner, that means their bot is returning to get fresh data from the foo.xml or bar.xml files. If so, we call the new ASP.NET 2.0 TransmitFile API, that supplements/improves on the earlier WriteFile. Transmit file doesn't buffer to memory and solved a number of problems seen when writing out files and buffering.

We also fall to this default code path if any other XML file has been requested, if that file is hooked into ASP.NET via the web.config. In my example, *.xml is hooked up, so all other requests end up in the TransmitFile API.

TransmitFile doesn't pass the request back to IIS, as some folks believe, nor does it handle caching for you. If you need that support you'll need to write it by following the HTTP spec and handling the headers yourself.

To recap:

  • Tell IIS to pass handling of your extension to ASPNET_ISAPI
  • Map files or extensions to Assembly Qualified Names in the web.config httpHandlers section - i.e. What class handles what files dynamically?
  • Create, redirect or serve your files

I'll try to writeup some other ways to solve the same problem, depending on what version of ASP.NET and IIS you're using. If you've got clever ways (there are many) leave them in the comments.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, February 01, 2007 3:02:44 PM UTC
Scott:

What are you using to copy and paste code snippets in your blog? Are you using "Copy as HTML" or something else? Also, how do you deal with the white-on-black display settings you use (which I'm using too and loving)? Do you load different settings before the copy, or do you have a slicker way of doing it?

Thanks, Dave
Dave P.
Thursday, February 01, 2007 6:31:49 PM UTC
Well, depending on what you want to do exactly, if you have the flexibility of providing any arbitrary URL for the dynamic XML files, you can name them "*.xmlx". Then you can do basically what you have done, but only map the XMLX requests through the handler. In other words, for existing (static) XML files, IIS would handle it. You wouldn't need the default code path and your ASP.NET app might be able to be saved from firing up occasionally when only static files were requested.
Tim K
Thursday, February 01, 2007 6:55:08 PM UTC
Tim K - Totally, if you don't already have an existing permalink, then another extension would be ideal.

Dave P - I'm using Omar's "Insert Code" plugin to Windows Live Writer.
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.