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:
PublishingWeb.GetPublishingWeb()
wraps an existingSPWeb
reference that should already have a disposal plan.PublishingWeb.GetVariation()
creates an internalSPWeb
that is disposed onClose()
.PublishingWeb.ParentPublishingWeb
creates an internalSPWeb
that is disposed onClose()
, but also references itsWeb
‘sParentWeb
, which may or may not need to be disposed.PublishingWebCollection
creates an internalSPWebCollection
from which it pullsSPWeb
s that are not disposed onClose()
of the childPublishingWeb
s.PublishingWebCollection.Add()
creates an internalSPWeb
that is disposed onClose()
.
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:
- 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.
- Internally
ParentPublishingWeb
uses the “owned” constructor, so we need to callClose()
. However, it gets the parameters for that constructor from itsWeb
‘sParentWeb
property. If we’re in a situation where we own the originalSPWeb
, we also now own itsParentWeb
and will need toDispose()
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.