

Presentation Inversion of Control - Part 2


Ben Golden
Ben Golden

In my previous post, I introduced the concept of presentation inversion of control.  In this post, I will show a few ways that this concept can be implemented.

I will cover three different implementations of presentation inversion of control. However, all of the implementations follow the same basic procedure. The three implementations only differ in how and when they execute these steps.

  1. Get the list of content items that need to be rendered on the page.
  2. Look up the presentation settings on those items.
  3. Add the rendering controls specified in the presentation settings to the page.

Step one will be pretty much the same for all three implementations. For the callouts example that I used in part 1, the content author selects the items that they want to add to the page in a multilist or treelist. So, step one would just consist of getting the list of items selected in that field. In other scenarios, you might get the list of child items or items in a particular folder.  In the sample code in this post I will abstract the detail of step one through the use of a custom item property.


When I first came up with the concept of presentation inversion of control, this is how I implemented it. This was back in 2007 when we were using Sitecore 5.3.  The basic idea here is that your logic is contained within a sublayout or web control.  You look up the presentation settings on each of the items that you want to render on the page, get the control responsible for rendering and add it as a child control of your sublayout or web control.

01.using Sitecore.Data.Items;
02.using Sitecore.Layouts;
03.using Sitecore.Pipelines.RenderLayout;
04.namespace BlogPosts.Pioc
05.    public partial class Callouts: System.Web.UI.UserControl
06.    {
07.        protected void Page_Load(object sender, EventArgs e)
08.        {
09.            PageItem page = Sitecore.Context.Item;
10.            foreach (Item itemCallout in page.RightCallouts)
11.           {
12.                string strDataSource = itemCallout.ID.ToString();
13.                RenderingReference[] renderings =
14.                    itemCallout.Visualization.GetRenderings(Sitecore.Context.Device, false);
15.                foreach (RenderingReference rendering in renderings)
16.                {
17.                    rendering.Settings.DataSource = strDataSource;
18.                    this.Controls.Add(
19.                        rendering.RenderingItem.GetControl(rendering.Settings));
20.                }
21.            }
22.        }
23.    }

The code above iterates through the list of items and for each item, it iterates through the list of renderings that are assigned in its presentation settings. For each rendering, it sets the data source to the item ID, gets the control associated with the rendering and adds it to the controls collection.

This implementation does the job. All of the content items are rendered as expected.  You can use all of the standard Sitecore caching settings. It even still works with the Page Editor (Web Edit, if you’re still on version 5.x).

One downside to this approach is that the rendering controls are added very late in the pipeline. It would be nice if we could add them around the same time as the rest of the renderings so that they would be available for any other processing that you wanted to do on them.

RenderLayout Processor

The next implementation uses a custom proccessor for the RenderLayout pipeline. The code is very similar to the first implementation except that it adds the RenderingReference objects directly to the page context rather than using them to get the actual controls.

01.using Sitecore.Data.Items;
02.using Sitecore.Layouts;
03.using Sitecore.Pipelines.RenderLayout;
05.namespace BlogPosts.Pioc
07.    class InsertCalloutRenderings : RenderLayoutProcessor
08.    {
09.        public override void Process(RenderLayoutArgs args)
10.        {
11.            PageItem page = Sitecore.Context.Item;
12.            foreach (Item itemCallout in page.RightCallouts)
13.            {
14.                string strDataSource = itemCallout.ID.ToString();
15.                RenderingReference[] renderings =
16.                    itemCallout.Visualization.GetRenderings(Sitecore.Context.Device, false);
17.                foreach (RenderingReference rendering in renderings)
18.                {
19.                    rendering.Settings.DataSource = strDataSource;
20.                    Sitecore.Context.Page.AddRendering(rendering);
21.                }
22.            }
23.        }
24.    }

This is then inserted into the RenderLayout pipeline, usually right after the InsertRenderings processor.

2.  ...
3.  <processor type="Sitecore.Pipelines.RenderLayout.InsertRenderings, Sitecore.Kernel" />
4.  <processor type="BlogPosts.Pioc.AddCallouts, BlogPosts.Pioc" />
5.  <processor type="Sitecore.Pipelines.RenderLayout.PageExtenders, Sitecore.Kernel" />
6.  ...

This is an improvement over the first implementation because the renderings are added much earlier in the pipeline. One disadvantage of this implementation is that it runs on every request.  Therefore, you need to make sure that your code is efficient. It is a good idea to start out with some conditional statements so that you can exit immediately, if your processor doesn’t need to run.

Conditional Rendering

In Sitecore 6.1 and later, we have another implementation option: conditional rendering. A conditional rendering rule associates a condition with an action. The rule can then be selected in the Personalization section of the Control Properties dialog when editing presentation details.

Depending on the requirements, it may be possible to use out-of-the-box conditions. For the callouts example, I created a custom action that determines whether the specified multilist or treelist field is empty, taking into account the permissions of the user.

01.using Sitecore.Data.Fields;
02.using Sitecore.Diagnostics;
03.using Sitecore.Rules;
04.using Sitecore.Rules.Conditions;
06.namespace BlogPosts.Pioc
08.    class WhenMultilistEmpty<T> : WhenCondition<T> where T : RuleContext
09.    {
10.        public string FieldName { get; set; }
12.        protected override bool Execute(T ruleContext)
13.        {
14.            Assert.ArgumentNotNull(ruleContext, "ruleContext");
15.            Assert.IsNotNullOrEmpty(this.FieldName, "FieldName is not set");
17.            MultilistField fld = ruleContext.Item.Fields[this.FieldName];
18.            Assert.IsNotNull(fld, string.Format("Field {0} does not exist", this.FieldName));
19.            return (fld.GetItems().Length == 0);
20.        }
21.    }

The condition definition item then has the following Text property. The part in square brackets tells the system to let the user enter a string for the name of the field that should be checked. This is how the FieldName property above is set.


The action is then very similar to the previous two implementations. This time we add the rendering references to the rule context. For the sake of reuse, I created an abstract base class for this action. This makes it a bit easier to create actions that get the list of non-page items in some other way.

01.using System.Collections.Generic;
02.using Sitecore.Data.Items;
03.using Sitecore.Layouts;
04.using Sitecore.Rules.Actions;
05.using Sitecore.Rules.ConditionalRenderings;
07.namespace BlogPosts.Pioc
09.    public abstract class AddSelfRenderingItemsActionBase<T>
10.        : RuleAction<T> where T : ConditionalRenderingsRuleContext
11.    {
12.        public abstract IEnumerable<Item> GetItems(T ruleContext);
14.        public override void Apply(T ruleContext)
15.        {
16.            foreach (Item item in this.GetItems(ruleContext))
17.            {
18.                string strDataSource = item.ID.ToString();
20.                RenderingReference[] calloutRenderings =
21.                    item.Visualization.GetRenderings(Sitecore.Context.Device, true);
22.                foreach (RenderingReference rendering in calloutRenderings)
23.                {
24.                    rendering.Settings.DataSource = strDataSource;
25.                    ruleContext.References.Add(rendering);
26.                }
27.            }
28.        }
29.    }

An implementation of the abstract class gets the items selected in the specified multilist or treelist field.

01.using Sitecore.Data.Items;
02.using Sitecore.Diagnostics;
03.using Sitecore.Rules.ConditionalRenderings;
05.namespace BlogPosts.Pioc
07.    public class AddSelfRenderingItemsFromMultilistAction<T>
08.        : AddSelfRenderingItemsActionBase<T> where T : ConditionalRenderingsRuleContext
09.    {
10.        public string FieldName { get; set; }
12.        public override IEnumerable<Item> GetItems(T ruleContext)
13.        {
14.            Assert.IsNotNullOrEmpty(this.FieldName, "Field name not set");
15.            Item pageCurrent = ruleContext.Item;
16.            MultilistField fld = pageCurrent.Fields[FieldName];
17.            return fld.GetItems();
18.        }
19.    }

The action definition item allows the user to enter the name of the field.  This is how the FieldName property above is set.


A rule definition item then puts the condition together with the action.


Finally, the rule is selected in the Control Properties dialog of a sublayout.


Obviously, there is a bit more effort required for this implementation. At least, the first time around. But this is composed of pieces that could be reused individually. This implementation also has advantages over the other two. First, the renderings will be added during the InsertRenderings step of the RenderLayout pipeline at the time that renderings are normally added. Also, since you can select this rule to be evaluated on another sublayout or rendering, it won’t necessarily be executed on every request.

In my opinion, the conditional rendering implementation gives the cleanest, most efficient and most maintainable result.  If you are still working with an older version of Sitecore, one of the first two implementations will still be a big improvement over the traditional approaches that I discussed in part 1.

Let's Get Started


