Out of the box, Sitecore creates and utilizes subclass instances of Sitecore.Layouts.PageExtenders.PageExtender — this class resides in Sitecore.Kernel.dll — to hook in functionality for Preview and Debugging features of the Page Editor — check out this post by John West where John discusses these — and all PageExtender instances are called upon to insert their controls onto pages via the PageExtenders pipeline processor in the renderLayout pipeline.
Last night, I hankered to explore the possibility of using a custom PageExtender outside of Sitecore’s Page Editor as an alternative route for placing content onto rendered pages in my local Sitecore sandbox instance.
I pretended I was meeting a business requirement for a fictitious company that has a website where important information is to be displayed in a big red box at the top of every page to its website visitors when applicable.
Before building my PageExtender, I defined a template for items that will contain these important messages. I named my template Alert — I probably could have chosen a better name but decided to continue this one:
Alert items can contain alert copy in its Alert Text Single-Line Text field.
I then created an Alert item containing some important information for website visitors:
Now that we have content, it’s time to build our PageExtender to display this content:
using System; using System.Web.UI; using System.Web.UI.WebControls; using Sitecore.Configuration; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Layouts; using Sitecore.Layouts.PageExtenders; using Sitecore.Sites; using Sitecore.Web.UI.WebControls; namespace Sitecore.Sandbox.Layouts.PageExtenders { public class AlertBoxPageExtender : PageExtender { private static readonly string PlaceHolderKey = Settings.GetSetting("AlertBoxPageExtender.PlaceholderKey"); private static readonly string AlertBoxID = Settings.GetSetting("AlertBoxPageExtender.AlertBoxID"); private static readonly string AlertTextFieldName = Settings.GetSetting("AlertBoxPageExtender.AlertTextFieldName"); private RenderingReference _AlertBoxRenderingReference; private RenderingReference AlertBoxRenderingReference { get { if (_AlertBoxRenderingReference == null) { _AlertBoxRenderingReference = CreateNewAlertBoxRenderingReference(); } return _AlertBoxRenderingReference; } } public override void Insert() { if (CanAddAlertBox()) { Sitecore.Context.Page.AddRendering(AlertBoxRenderingReference); } } private bool CanAddAlertBox() { SiteContext site = Context.Site; return site != null && site.EnablePreview && site.DisplayMode == DisplayMode.Normal && AlertBoxRenderingReference != null; } private RenderingReference CreateNewAlertBoxRenderingReference() { Control alertBoxControl = CreateAlertBoxControl(); bool canCreateRenderingReference = alertBoxControl != null && !string.IsNullOrEmpty(PlaceHolderKey); if (canCreateRenderingReference) { return CreateNewRenderingReference(CreateAlertBoxControl(), PlaceHolderKey); } return null; } private Control CreateAlertBoxControl() { Control alertBoxInnerControl = CreateNewAlertBoxInnerControl(); if (alertBoxInnerControl != null) { Panel alertBoxPanel = new Panel(); alertBoxPanel.CssClass = "alert-box"; alertBoxPanel.Controls.Add(alertBoxInnerControl); return alertBoxPanel; } return null; } private Control CreateNewAlertBoxInnerControl() { Item alertBoxItem = TryGetAlertBoxItem(); bool canCreateInnerPanel = alertBoxItem != null && !string.IsNullOrEmpty(AlertTextFieldName) && !string.IsNullOrEmpty(alertBoxItem[AlertTextFieldName]); if (canCreateInnerPanel) { Panel alertBoxInnerPanel = new Panel(); alertBoxInnerPanel.Controls.Add(CreateNewFieldRenderer(alertBoxItem, AlertTextFieldName)); return alertBoxInnerPanel; } return null; } private static FieldRenderer CreateNewFieldRenderer(Item item, string fieldName) { Assert.ArgumentNotNull(item, "item"); Assert.ArgumentNotNullOrEmpty(fieldName, "fieldName"); return new FieldRenderer { Item = item, FieldName = fieldName }; } private static RenderingReference CreateNewRenderingReference(Control control, string placeholderKey) { Assert.ArgumentNotNull(control, "control"); Assert.ArgumentNotNullOrEmpty(placeholderKey, "placeholderKey"); RenderingReference renderingReference = new RenderingReference(control); renderingReference.Placeholder = placeholderKey; return renderingReference; } private Item TryGetAlertBoxItem() { try { return Sitecore.Context.Database.Items[AlertBoxID]; } catch (Exception ex) { Log.Error(this.ToString(), ex, this); } return null; } } }
The above PageExtender inserts an instance of a FieldRenderer — that will render content from our Alert item’s Alert Text field above — into nested ASP.NET Panels. I give the outer Panel a CSS class to position these ASP.NET controls at the top of the page, and decorate it with a red background and large white text font — I’ve omitted this CSS from this post for the sake of brevity.
The combined ASP.NET controls are then placed into an instance of the Sitecore.Layouts.RenderingReference class — also in Sitecore.Kernel.dll. This RenderingReference is tagged for insertion into the Sitecore placeholder I have defined in a setting within my patch include configuration file below:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pageextenders> <pageextender type="Sitecore.Sandbox.Layouts.PageExtenders.AlertBoxPageExtender, Sitecore.Sandbox"/> </pageextenders> <settings> <setting name="AlertBoxPageExtender.PlaceholderKey" value="main" /> <setting name="AlertBoxPageExtender.AlertBoxID" value="{389D13C8-9475-4F5A-819B-78BA51C62326}" /> <setting name="AlertBoxPageExtender.AlertTextFieldName" value="Alert Text" /> </settings> </sitecore> </configuration>
Let’s take this for a spin.
I created some page items and published. I then navigation to the first page item I created:
As you can see, our red alert box displays at the top of the page.
I then navigated to the other page I created for testing:
This page also contains our red alert box.
Now, lets remove the box. I deleted the copy from the Alert Text field in our Alert item in Sitecore, and published. I refreshed our second test webpage in my browser:
As you can see, our red alert box is now gone.
All of this is wonderful — inserting controls globally in this manner could potentially be a project saver when needing to add content to all pages in a pinch.
However, it’s also a double-edged sword. Such a solution might force the insertion of controls onto pages without the option of removing them using Sitecore’s presentation framework of adding/removing renderings.
Plus, developers who need to update this logic will have to fish around and find where this logic lives in your solution. Having to seek through the code-base to find where this logic lives could augment the time needed to complete the task of modifying this code — I’m not saying you don’t have excellent documentation articulating where this logic lives, although some people may not look at the documentation first, and will just start surfing through code to find it. I am guilty as charged for doing this myself. 🙂
Given these two salient factors, I would strongly recommend being conservative around using custom PageExtenders for displaying content on your Sitecore webpages.
Great post!
Really functional for all kind of announcements (or advertising 😉 ).
Thanks Pieter!
Advertising never crossed my mind when building this solution — although advertisements most definitely could be added to pages using PageExtenders.
I hope this post doesn’t kindle an onslaught of banner ads on Sitecore managed websites. 😉
[…] couldn’t find much information on PageExtenders apart from this single article on Custom PageExtender from fellow MVP Mike ‘Gold Suits’ Reynolds. PageExtenders allow you to insert any HTML […]