Refactoring PageContext

As you all know, writing software is a very dynamic process.  Designs change, users give feedback, and sometimes you are so buried in the implementation that you don't see flaws in the design.  Occasionally this turns out a design that is less than ideal.  When I stepped back to take a look at the PageSettings/PageContext arrangement I found I wasn't completely happy with the design.  It works perfectly but its usefulness is hindered by an unclear API. By this time we were beyond the point where I could refactor this kind of change so it stayed but this is a blog and I can show you how I would have made the change hopefully teaching you a little bit about the Starter Site and Refactoring in one post.

First, the "problems" with the API.  I originally created PageContext because I felt it was different than SiteContext.  SiteContext dealt with site-wide items and PageContext dealt with page-specific items.  This is a fair split.  Unfortunately the line is sort of blurry: is SiteContext.CurrentCategory a site-wide item or a page-specific item?  In addition, by the time we were done PageContext only had one static method (far fewer than I thought we had) and a handful of properties (only two if you collapse the PageSettings delegation).  In my first technical post on this blog I said that SiteContext is the launching-point for many operations on the site.  PageContext is one of the reasons the qualifier "many" is included.  Now instead of knowing about SiteContext you also have to know about PageContext for a couple of operations.  All of these reasons led me to the conclusion that the PageContext functionality should be wrapped up into SiteContext creating a single place to go for site operations.

Looking at the PageSettings class I see a couple of problems.  This class is useful beyond the "current page" concept but is designed to be used by PageContext directly and not publicly accessible.  I think this should be opened up to the public.  Also, using "Type" as a parameter is a little...weird.  It's not intuitive.  We'll switch to using the handler instance itself.  This will allow us more flexibility in extending the PageSettings class (see the alternate implementation footnote from the previous post).  We can even gather data directly from the handler and not just its type.

Enough talking, let's start coding.  I am using refactoring tools to make this job easier but you can do it by hand if you don't have any tools.  The built-in refactoring tools in Visual Studio will provide some of the operations I'll be performing.  For those following along with Fowler ("Refactoring: Improving the Design of Existing Code"), the refactorings we'll be doing are Move Method (142)  and Move Field (146) .

The first thing we want to do is gut the PageContext class and move it to SiteContext.  We'll start by moving PageContext.BuildProfileMenu(Page) to SiteContext.  This method is used in every page of the Profile subdirectory so you will need to update the references there if it is not done automatically by your tools.  Run a build to make sure everything compiles.  You can also run through your tests to ensure nothing is broken.  This is a pretty benign change so everything should be happy.

Next we'll move PageContext.IndustryCode and PageContext.Settings and their respective fields to SiteContext.  When we move the Setting property we'll rename it to make more sense (PageSettings ought to be a fine name) and make it public.  We're essentially removing the delegation that PageContext gave us (it's fine if you want to leave the delegation on SiteContext but I think it clutters the class too much).

Start with PageContext.IndustryCode.  Move it and its field to SiteContext.  Update references in StandardLayout.Master, Products.aspx.cs, and Browse.aspx.cs to use SiteContext.Current.IndustryCode.  Run a build.

When we move PageContext.settings to SiteContext.pageSettings we will need to move the initializer.  In this case I would use lazy (on-demand) initialization.  Your SiteContext.PageSettings property getter should look like this:

public PageSettings PageSettings
{
  get
  {
    if(this.pageSettings == null)
{
      this.pageSettings =
        PageSettings.GetPageSettings(HttpContext.Current.CurrentHandler.GetType());
}

    return this.pageSettings;
}
}

At this point all PageContext is being used for is the delegation (which is being pointed to SiteContext).  A project-wide search and replace for "PageContext.Current" to "SiteContext.Current.PageSettings" should be all that's needed to remove PageContext once and for all.  We'll also need to change PageSettings to turn all of its internal members into public members.  We'll discuss this more in the next post.

We now have one less class and have moved more request-specific properties and operations to SiteContext.  This should make it easier to find what you are looking for.  In my next post we'll tackle PageSettings and find ways to make it more useful and extensible.