An In-Depth Look At The Life of a Subtext Request

Contents

Introduction

In the entry entitled How an URL Is Mapped To A Blog we covered at a high level how urls are mapped to blogs. In this post we get into the nitty gritty and look at how Subtext handles requests at the code level. Much of this is still a legacy implementation from the code’s .TEXT days.

As an example, we’ll trace the code that executes to service a request for the following URL:

http://haacked.com/archive/2006/01/25/ISwear.aspx

This request will end up serving up the page for a single blog post.

First Stop, Application_BeginRequest

The first stop on our tour is the Application_BeginRequest method within Global.asax.cs.

This method attempts to set the current culture based on the languages specified by the user’s browser within the request headers.

Afterwards, it checks the state of the Subtext installation. If the installation is not complete, it will redirect to the install directory. Othwerwise execution continues normally.

Next Stop, Web.config Http Handlers

Next, the ASP.NET runtime takes a peek at the web.config to figure out which handler is responsible for handling the request. The list of handlers is configured within the <httpHandlers> section of the <system.web> section.

<add verb="*" path="Install/*.aspx" 
	type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="SystemMessages/*.aspx" 
	type="System.Web.UI.PageHandlerFactory" />

<add verb="*" path="HostAdmin/*.aspx" 
	type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="HostAdmin/*/*.aspx" 
	type="System.Web.UI.PageHandlerFactory" />

<add verb="*" path="Logout.aspx" 
	type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="BlogMLExport.ashx" 
	type="Subtext.Web.Admin.Handlers.BlogMLExport,
	 Subtext.Web" />

	    
<!-- This will process any ext mapped to aspnet_isapi.dll -->
<add verb="*" path="*" 
  type="Subtext.Common.UrlManager.UrlReWriteHandlerFactory, 
	Subtext.Common" />

The first five entries in this list are handled by the PageHandlerFactory. That is the default ASP.NET handler for an *.aspx page, so they execute like any normal request for a *.aspx page would. These correspond to system pages and subdirectories that Subtext does not perform URL Rewriting on.

The very last request maps all other requests to the UrlReWriteHandlerFactory. Note, by “all other requests” we mean all other requests with a file extension that IIS normally hands off to the ASP.NET runtime.

Since the request we are following does not match any of the first five handlers, it is handled by the UrlReWriteHandlerFactory.

All Aboard The UrlReWriteHandlerFactory

The UrlReWriteHandlerFactory is somewhat misnamed as it doesn’t actually rewrite URLs. Instead, it is used to map URLs to controls.

This handler has its own configuration section within web.config. It is defined within the <HandlerConfiguration> section. It has a single <HttpHandlers> node which itself has multiple <HttpHandler> nodes.

The code below is a small snippet from the HttpHandlers section.

<HttpHandler 
  pattern="(?:/archive/\d{4}/\d{2}/\d{2}/\d+\.aspx)$" 
  controls="viewpost.ascx,Comments.ascx,PostComment.ascx" />
<HttpHandler 
  pattern="(?:/archive/\d{4}/\d{2}/\d{2}/[-_,+\.\w]+\.aspx)$" 
  controls="viewpost.ascx,Comments.ascx,PostComment.ascx" />
<HttpHandler
  pattern="(?:/archive/\d{4}/\d{1,2}/\d{1,2}\.aspx)$" 
  controls="ArchiveDay.ascx" />

Each <HttpHandler> has a pattern property which is in the form of a regular expression. If the incoming request matches the pattern, then that handler handles the request.

This pattern matching occurs within the GetHandler method of the UrlReWriteHandlerFactory class. Once a matching handler is found, its handlerType property is looked at. If it is not specified in the config settings, it has a value of “Page” by default.

In our case, the request we are following matches the pattern in the second handler above (in bold). That handler has a handlerType of “Page”, which causes the the factory to call the ProcessHandlerTypePage method.

ProcessHandlerTypePage then adds the list of controls configured in the handler’s controls property and calls System.Web.UI.PageParser.GetCompiledPageInstance. This returns a compiled instance of “DTP.aspx”. Remember, all this code is executing within a factory which has the single goal of returning an IHttpHandler to the ASP.NET runtime.

Scott Watermasysk, the original creator of .TEXT wrote up a short description of this technique of URL Rewriting here.

DTP.aspx Ahoy!

Our journey is by no means over yet, mate. DTP.aspx is the base skeleton template for pretty much every page that is handled by the UrlReWriteHandlerFactory.

It has several placeholder server controls for such things as link tags to stylesheets, auto discoverable RSS tag, javascripts, and more.

It also has a MasterPage control declared with a ContentRegion. These act very similar to the corresponding controls in ASP.NET 2.0.

Who Is The Master Page!?

The next stop in our travels takes us to the Subtext.Web.UI.WebControls.MasterPage control declared on DTP.aspx. This control is an adaptation of Paul Wilson’s Master Page control.

In the OnInit method, the MasterPage control attempts to load in a template control. This control defines the template for the current skin. However, at this point, we have to take a diversion as Subtext does not yet know which blog we need to load the skin for. When attempting to look up the current skin, Subtext calls the property Config.CurrentBlog

Who Am I?

Config.CurrentBlog calls the GetBlogInfo method on the specified Configuration provider (there is only one, the Subtext.Framework.Configuration.UrlBasedBlogInfoProvider).

The UrlBasedBlogInfoProvider is responsible for mapping URLs to Blogs as described in How An Url Is Mapped To A Blog. This all happens in the GetBlogInfo method.

Slap Me Some Skin

Now that we know which blog the request is for, we can get the skin name of the current blog from the BlogInfo.Skin.SkinName property. The SkinName property corresponds to the subdirectory of the “Skins” directory in which files for the current skin are located.

The TemplateFile referred to earlier is a file named “PageTemplate.ascx” found in the skin directory.

The master page then loads this template control, and removes the template subcontrols, adds those controls to itself, and finally adds the template to its own controls collection at position 0.

The next step is to move these controls into the ContentRegion named MPMain.

DTP.aspx and SKin Controls

Ok, finally we are back at the codebehind for the DTP.aspx page. The code behind class is Subtext.Web.UI.Pages.SubtextMasterPage.

The initialization method for this page looks at the Skin configuration file (/Admin/Skins.config) to find out which javascript and css files need to be referenced. It places references to these files in the <head> section of the page.

The method then loads each control specified by the HttpHandler (remember that guy?) into the center body control. In this example, the page should load in the following controls:

  • viewpost.ascx
  • Comments.ascx
  • PostComment.ascx

These are skin controls that are loaded from the current Blog’s skin directory.

More On Skins

Skin controls in Subtext are simply user controls. They do not have a code-behind file but instead inherit from classes defined in the “Subtext.Web/UI/Controls” directory.

Each skin directory is responsible for containing the “Code In Front” ascx file for each of these controls it makes use of.

For example, the Redbook skin directory and the Piyo skin directory each will contain a “ViewPost.ascx” file. The respective files may differ in layout, but they will have the same Inherits declaration at top like so:
<%@ Control Language="c#" 
    Inherits="Subtext.Web.UI.Controls.ViewPost" %>

So when DTP.aspx is loading in these skin controls from the proper skin directory, each control will execute its OnInit method as well as its OnLoad at the right time.

Subtext Logo design by TurboMilk.