Enterprise MOSS 2007 Performance Gotcha: ConsoleNode / Editing Toolbar Customization

There is a severe problem with the SharePoint 2007 ConsoleNode. In particular, you can run into an astonishing performance loss when attempting to build a dynamic menu that can change depending on page state. The Microsoft.SharePoint.Publishing.WebControls.ConsoleNode class is a base class that you inherit to customize the SharePoint Editing Toolbar.

image

In the above image, the “Page”, “Workflow”, and “Tools” options, for example, are ConsoleNodes.

The high level implementation goes like this:

  1. Create a class – let’s say, MyConsoleNode – that inherits from Microsoft.SharePoint.Publishing.WebControls.ConsoleNode.
  2. Override the GET property accessor for ChildConsoleNodes, shown below.

    image

  3. When SharePoint requests the ChildConsoleNodes property, you build a few child nodes that will be displayed in the dropdown menu, and add them to the base.ChildConsoleNodes collection.

This is the pattern that Microsoft uses for their Out-of-the-Box console nodes as well.

The Problem

This appears to be a fairly straightforward pattern, and works well, until you start doing any significant calculations to build your Child Node collection. The reason is that, in my testing, this property gets called 12+ times per page load. Hang on, because this journey is not over…

Most normal developers would solve this problem with a simple boolean flag, say in the current HTTP request, that indicates whether or not you have already built your child nodes, and if so, don’t do it again. Do NOT do this! You will most likely end up with a completely blank child menu, and start to question why you became a SharePoint developer.

This is the way that MyConsoleNode is used by SharePoint when it renders the page; my best guess is that it’s done this way to calculate permissions. For the purposes of my example, we’ll assume that 2 child console nodes are built.

  1. HTTP request begins, and SharePoint starts to build the page.
  2. A new instance of MyConsoleNode is instantiated (we’ll call this Instance A).
  3. GET ChildConsoleNodes is called (Instance A), no existing child nodes.
  4. GET ChildConsoleNodes is called (Instance A), 2 child nodes are already present.
  5. A new instance of MyConsoleNode is instantiated (Instance B).
  6. GET ChildConsoleNodes is called (Instance B), no existing child nodes.
  7. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  8. GET ChildConsoleNodes is called (Instance B), 0 child nodes already present.
  9. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  10. GET ChildConsoleNodes is called (Instance B), 3 child nodes already present [don’t ask – I don’t know].
  11. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  12. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  13. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  14. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  15. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present.
  16. Page is rendered.

It’s fairly obvious why a simple boolean flag will not work, for two reasons:

  • The class is instantiated twice (once in Step 2, again in Step 5).
  • Even when the class is not instantiated again, in Step 8, the previously added child nodes are gone.

There’s another mystery of why there are 3 child nodes in Step 10, but I didn’t really care to research that one.

The Solution

If you want to solve this problem, you will, unfortunately, have to use reflection to get an internal property value. I am not going to post the code to do this, because it is not guaranteed to survive a MOSS Service Pack upgrade or translate into future versions of SharePoint, and you will have to make some decisions in your code as to what to do if this property disappears at some point.

The key to solving this problem is a boolean internal property of ConsoleNode called ReadyToCreateChildNodes.

image

If you look at a Microsoft out-of-the-box Console Node (for example, let’s check out ModifyWebPartsNode) you’ll see a method called EnsureChildNodes():

image

In the code of this method, Microsoft internally checks for this condition:

image

As a side note, in my testing, I did also duplicate the ConsoleNode.IsValidStateForNode conditions, and they were always true; however, this depends on the permissions you’ve applied to the node, and I was using EmptyMask. But for the purposes of solving this problem, all you really need to care about is ReadyToCreateChildNodes.

Let’s look at the request again, now considering the state of this property.

  1. HTTP request begins, and SharePoint starts to build the page.
  2. A new instance of MyConsoleNode is instantiated (we’ll call this Instance A).
  3. GET ChildConsoleNodes is called (Instance A), no existing child nodes. [ReadyToCreateChildNodes = FALSE]
  4. GET ChildConsoleNodes is called (Instance A), 2 child nodes are already present. [ReadyToCreateChildNodes = FALSE]
  5. A new instance of MyConsoleNode is instantiated (Instance B).
  6. GET ChildConsoleNodes is called (Instance B), no existing child nodes. [ReadyToCreateChildNodes = FALSE]
  7. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  8. GET ChildConsoleNodes is called (Instance B), 0 child nodes already present. [ReadyToCreateChildNodes = TRUE]
  9. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  10. GET ChildConsoleNodes is called (Instance B), 3 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  11. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  12. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  13. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  14. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  15. GET ChildConsoleNodes is called (Instance B), 2 child nodes already present. [ReadyToCreateChildNodes = FALSE]
  16. Page is rendered.

Occasionally, in my testing, ReadyToCreateChildNodes in step #9 would also be in a “true” state, but for the most part, it was only true for one call. And once I built my child nodes in Step 8, they remained populated for the rest of the request.

So, to sum this all up, the solution to this problem is: Use reflection to check the internal boolean ReadyToCreateChildNodes property before you build your child nodes.