The Semi-Disposable PublishingWeb

As if handling SPSite and SPWeb isn’t tedious enough, MOSS’s PublishingWeb class throws another wrench into things. Its constructors are internal, so references to it always come from within the framework. I’m currently aware of five sources of these objects:

  1. PublishingWeb.GetPublishingWeb() wraps an existing SPWeb reference that should already have a disposal plan.
  2. PublishingWeb.GetVariation() creates an internal SPWeb that is disposed on Close().
  3. PublishingWeb.ParentPublishingWeb creates an internal SPWeb that is disposed on Close(), but also references its Web‘s ParentWeb, which may or may not need to be disposed.
  4. PublishingWebCollection creates an internal SPWebCollection from which it pulls SPWebs that are not disposed on Close() of the child PublishingWebs.
  5. PublishingWebCollection.Add() creates an internal SPWeb that is disposed on Close().

Internally, PublishingWeb is set up relatively well to handle the complexity of wrapping a Disposable object. It provides two constructors: one that accepts an SPWeb, which it stores internally but does not mark as “owned”; and one that accepts a Uri and SPSite, from which it opens an SPWeb (and SPSite if necessary) that are marked as “owned” to be cleaned up later. This job is performed by Close(), which is even kind enough to add a trace message (level Medium) if an attempt is made to close a non-owned web:

PublishingWeb.Close(): currentWeb is owned by caller, so doing nothing. May indicate SPWeb leak in calling code.

One could argue that PublishingWeb should really be IDisposable, calling Close() from Dispose() (like SPWeb does), but for now we can get by with try...finally.

The Good

The internal ownership mechanism serves its purpose wonderfully for GetPublishingWeb(), GetVariation() and PublishingWebCollection.Add(). The former just defers to the SPWeb-accepting constructor with no need to close, and the latter two use the Uri+SPSite constructor and can be closed without hesitation.

using(SPWeb web = site.OpenWeb())
{
  PublishingWeb varPubWeb = null
  PublishingWeb newPubWeb = null;
  try
  {
    PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);

    VariationLabel label = Variations.Current[0];
    varPubWeb = pubWeb.GetVariation(label);
    // Process varPubWeb

    PublishingWebCollection pubWebs = pubWeb.GetPublishingWebs();
    newPubWeb = pubWebs.Add("NewPubWeb");
  }
  finally
  {
    // pubWeb.Close() would generate a trace warning
    if(null != varPubWeb)
      varPubWeb.Close();
    if(null != newPubWeb)
      newPubWeb.Close();
  }
}

The Bad

Things get a bit trickier with ParentPublishingWeb for two reasons:

  1. We are subject to all the usual concerns when dealing with SPWeb.ParentWeb:

    This property will allocate an SPWeb object the first time it is called. The caveat is that once it is disposed, any reference to the property will return the disposed object. If an SPWeb is not owned by the developer, its ParentWeb should be considered not owned as well. For example, there could be a problem if two components both depend on SPContext.Current.Web.ParentWeb and one calls Dispose() before the other is done with it.

  2. Internally ParentPublishingWeb uses the “owned” constructor, so we need to call Close(). However, it gets the parameters for that constructor from its Web‘s ParentWeb property. If we’re in a situation where we own the original SPWeb, we also now own its ParentWeb and will need to Dispose() it.

In a case where we definitely own the web and its ParentWeb, we need to do something like this:

using(SPWeb web = site.OpenWeb())
{
  PublishingWeb parentPubWeb = null;
  try
  {
    PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);
    parentPubWeb = pubWeb.ParentPublishingWeb;
    // Process parentPubWeb
  }
  finally
  {
    if(null != parentPubWeb)
      parentPubWeb.Close();
    if(null != web.ParentWeb)
      web.ParentWeb.Dispose();
  }
}

The Ugly

Given how hard PublishingWeb tries to do the right thing, I’m surprised how little PublishingWebCollection does to help us. It too has an internal constructor, so we can only get a collection from PublishingWeb.GetPublishingWebs(), which passes in the current web’s GetSubwebsForCurrentUser(). This SPWebCollection is then used to build the PublishingWeb objects returned by indexing into or enumerating over the collection. However, unlike PublishingWebCollection.Add(), the indexers use GetPublishingWeb(SPWeb) and thus the “not my problem” constructor. So rather than rely on Close(), we need to manually call Dispose() on the wrapped Web:

using(SPWeb web = site.OpenWeb())
{
  PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);
  PublishingWebCollection pubWebs = pubWeb.GetPublishingWebs());
  foreach(PublishingWeb innerPubWeb in pubWebs)
  {
    try
    {
      // Process innerPubWeb
    }
    finally
    {
      // innerPubWeb.Close() would generate a trace warning
      innerPubWeb.Web.Dispose();
    }
  }
}

Update 1/29/2009: Roger Lamb has added this GetVariation() guidance to his Dispose Patterns by Example.

Advertisement