codeflood logo

Composite Presentation Inheritance

I have often wanted a way in Sitecore to be able to define most of my pages presentation in a base template but customise that presentation on more specific items.

Sitecore has a facility to inherit default values through the use of data template standard values. This mechanism works for all fields including presentation (__renderings field). Sitecore 6 introduced inheritance through standard values meaning that if a base template defines a particular standard value and the standard value in a derived template is null, the derived template will inherit the standard value for that field from it's base template.

This is really cool and makes maintenance a lot easier. Rather than having to set the default value per template, I can set the value on a base template and let inheritance through the standard values take care of the rest. For example, let's say I have a "show on sitemap" checkbox for all page type items. I have a base template called page base and derived templates called article and news. I'll set the default value for "show on sitemap" to true on the page base template's standard values. Because I don't set this on the article or news templates they inherit through their standard values the standard value of the parent template's field.

This mechanism is also very handy when it comes to defining presentation for your templates. You can define the basic presentation on a base template and then when you come to define the presentation on derived templates you already have a starting point as this initial presentation is inherited from the base template. The problem is, as soon as you adjust the presentation on a derived template you are then overridding the standard values of the base template so changes to this base template will not flow through to the derived templates.

Towards the end of last year I proposed a solution to this problem. But a recent post by Thomas Eldblom gave me an idea of how I might be able to solve this problem. Thomas described to us his "composite renderings" in which he defines a new presentation type which allows him to place a group of controls in presentation as a set. He then updated the renderLayout pipeline to expand the composite renderings when a request was made and the presentation for an item was being built. I could use a similar approach to realise my "Composite Presentation Inheritance".

Instead of expanding out groups of controls I'll instead need to pull in presentation from a parent template's standard values and aggregate them with that of the current item. To specify that composite presentation inheritance is being used, similar to Thomas' approach I'll define a special layout to be used on the item.

Let's start by defining this special layout called "Inherit".

inherit-layout

It doesn't matter what is in the ASPX file this layout refers to as it will never be used.

Next we need to ensure the correct layout is used when an item which uses the inherit layout is requested. The best place to do this would be immediately after Sitecore has resolved which layout is being used, and swap out inherit for the layout we need.

The layout is resolved during the httpRequestBegin pipeline by the LayoutResolver processor. So we'll need to create an HttpRequest processor which we can insert into the pipeline immediately after the layout resolver has run.

The following class shows an implementation for our inheritance layout resolver.

using System;
using System;
using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Data.Items;
using Sitecore.Pipelines.HttpRequest;

namespace Composite_Presentation_Inheritance
{
  public class InheritLayoutResolver : LayoutResolver
  {
    public override void Process(HttpRequestArgs args)
    {
      if (Context.Item != null)
        ProcessItem(Context.Item, true);
    }

    protected void ProcessItem(Item item, bool contentItem)
    {
      if (item.Visualization.Layout.Name.ToLower() == "inherit")
      {
        var baseTemplates = item.Template.BaseTemplates;
        for (int i = 0; i < baseTemplates.Length; i++)
        {
          if (baseTemplates[i].StandardValues != null)
            ProcessItem(baseTemplates[i].StandardValues, false);
        }
      }
      else if(!contentItem)
      {
        Context.Page.FilePath = item.Visualization.Layout.FilePath;
        Tracer.Info("Swapped inherit layout to " + Context.Page.FilePath);
      }
    }
  }
}

Note above how I recursively call the process item method with the item's base templates standard values. I use a recursive method to make sure we could support any number of inheritance levels through layout. If the layout of the current item (whether that be the item itself or a set of standard values) uses the inherit layout, continue the recursion path, otherwise bail out. We also need to do checks to ensure we only process if we're requesting content. This same resolver will run for any requests made for the Sitecore interfaces themselves, so this check makes sure we don't upset normal Sitecore behavior.

That's the layout done, now we need to aggregate the presentation controls. The presentation for the page being requested in build in the renderLayout pipeline by the InsertRenderings processor. We need to create a renderLayout pipeline processor which will recursively aggregate our presentation through each set of standard values in the template hierarchy of the current item, but only while the layout of the standard values is our special inherit layout. The following class shows an implementation for this.

using System;
using Sitecore.Pipelines.RenderLayout;
using Sitecore;
using Sitecore.Data.Items;

namespace Composite_Presentation_Inheritance
{
  public class PresentationAggregator : RenderLayoutProcessor
  {
    public override void Process(RenderLayoutArgs args)
    {
      if(Context.Item != null)
        process item(Context.Item, false);
    }

    protected void process item(Item item, bool addRenderings)
    {
      if (item.Visualization.Layout.Name.ToLower() == "inherit")
      {
        var baseTemplates = item.Template.BaseTemplates;
        for (int i = 0; i < baseTemplates.Length; i++)
        {
          if(baseTemplates[i].StandardValues != null)
            process item(baseTemplates[i].StandardValues, true);
        }
      }

      if (addRenderings)
      {
        var renderings = 
          item.Visualization.GetRenderings(Context.Device, true);
        for (int i = 0; i < renderings.Length; i++)
        {
          Context.Page.AddRendering(renderings[i]);        }
      }
    }
  }
}

Note how we do the recursive call before we add the renderings. This will have the effect of adding the standard values renderings from the very base template down through the hierarchy to our more specific templates. I also have a condition to check if the renderings should be added as we want to leave the adding of the item's renderings to the default processor. The default processor also takes care of adding in other controls to support the Sitecore interfaces.

Now all that's left is to configure our installation to use these new components. Jump into your web.config and find the httpRequestBegin pipeline and the LayoutResolver processor in that pipeline. We want to insert our composite presentation inheritance layout resolver immediately after it in the pipeline to allow us to swap the layout if the inherit layout is used.

<processor 
  type="Composite_Presentation_Inheritance.InheritLayoutResolver, 
  Composite Presentation Inheritance" />

Next we need to add our presentation aggregator into the renderLayout pipeline just before the InsertRenderings processor.

<processor 
  type="Composite_Presentation_Inheritance.PresentationAggregator, 
  Composite Presentation Inheritance" />

Our processor is inserted before the default processor to ensure our inherited renderings are placed into the layout before the item specific renderings.

And that's it. We now have an implementation of a composite presentation inheritance. I can now define presentation on a base template and make it more specific by adding specific renderings for derived templates.

I'll illustrate this by adding a new "specific page" template to good old Nicam which will inherit from simple page. The purpose of this new template is to provide a "related pages" field and rendering. All I need for the presentation for this new template is to add a new related pages rendering after the main content area. Let's review the simple page presentation.

simple page presso

I want my related links rendering to be inserted after the SimpleText rendering above which is bound into the /phcontent/phcenter placeholder. So in my "specific page" presentation I need to use the inherit layout so we inherit the base templates presentation and then add in the related links rendering.

specific page pres

And now, requesting an item based on the "specific page" template I get the presentation combined.

specific page

One consideration if you use this technique in production is to keep in mind that the rendering now takes more time as it's doing more. A potential performance tweak to address this concern would be to supplement the pipeline processors with an event handler that would aggregate the presentation on composite presentation inheritance layouts and store the resulting presentation into the item itself inside the publishing target databases when a publish operation finished. You still need the pipeline processors to allow content authors to preview their content appropriately and the publish:end event handler to enhance performance when using this technique.

Comments

Paul

First, this is totally awesome! Nice work.
A few questions...
What if I wanted to...: - Re-order/remove some of the inherited layout controls? - Wrap (instead of just appending) layout controls around the inherited set? Like:
MySpecificHeader + InheritedControls + MySpecificFooter

Alistair Deneys

Thanks Paul.
I can think of a few approaches to remove inherited controls and reorder controls as well. All approaches rely on the fact that we have access to the parameters of the presentation controls as we build up the page's presentation. So in the case of ordering we could define a "presentation order" parameter which we could use to sort the renderings before we add them to the page in our renderLayout processor. Of course we'd have to collect the controls instead of adding them directly to the page as there's no mechanism to remove them from the page once added. Same goes for removing inherited controls. We can define a "presentation remove" parameter which would instruct our processor to not add the control. The ordering approach should allow for wrapping the inherited controls as well; MySpecificHeader on the item would have sort order of 50, InheritedControls coming from the base template could have sort order of 100 and MySpecificFooter on the item a sort order of 150.

[...] Deneys in his blog proposed the so called Composite Presentation Inheritance to solve this problem. His approach works fine, but it affects rendering performance and uses [...]

Leave a comment

All fields are required.