Dynamic Sitecore MVC Placeholders
No CommentsSo about a month ago I ran into a well known dynamic placeholder issue with Sitecore. If you aren't aware of this issue I encourage you to check out previous posts from Matthew Kenney, John Newcombe, Nick Wesselman and David Leigh. My current project however is using Sitecore MVC and these solutions cover webforms implementations.
When I solicted feedback via Twitter Nick Wesselman was kind enough to point out this Stackoverflow post which appeared to be exactly what was needed, so we (co-worker Joe and myself) set out to get this integrated. There were a few wrinkles with the code/configuration as listed so I figured it was a good idea to document what was needed to get this working correctly.
As the first issue we ran into was with configuration I'm starting off with the proper overrides configuration. This is what our custom Pipelines.config file looks like:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<getPlaceholderRenderings>
<processor patch:before="*[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel']"
type="MyProject.Custom.Pipelines.GetPlaceholderRenderings.GetDynamicKeyAllowedRenderings, MyProject.Custom"
/>
</getPlaceholderRenderings>
<getChromeData>
<processor patch:after="*[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"
type="MyProject.Custom.Pipelines.GetChromeData.GetDynamicPlaceholderChromeData, MyProject.Custom"
/>
</getChromeData>
</pipelines>
</sitecore>
</configuration>
The following extension method adds a new DynamicPlacholder method to the SitecoreHelper class which creates the dynamic placeholder key by appending the unique id from the rendering to the key passed in. The code for this is shown below:
public static class DynamicPlaceholderExtension
{
public static HtmlString DynamicPlaceholder(this SitecoreHelper helper, string dynamicKey)
{
string DynamicKey = GetDynamicKey(dynamicKey);
if (!String.IsNullOrWhiteSpace(DynamicKey)) dynamicKey = DynamicKey;
Guid currentRenderingId = RenderingContext.Current.Rendering.UniqueId;
return helper.Placeholder(String.Format("{0}_{1}", dynamicKey, currentRenderingId));
}
private static string GetDynamicKey(string placeHolderName)
{
bool NeedIncrement = false;
int IncrementStep = 0;
IEnumerable<PlaceholderContext> myPlaceholders = ContextService.Get().GetInstances<PlaceholderContext>();
foreach (PlaceholderContext context in myPlaceholders)
{
if (context.PlaceholderName == placeHolderName ||
context.PlaceholderName.StartsWith(placeHolderName + "_"))
{
NeedIncrement = true;
IncrementStep++;
}
}
if (NeedIncrement)
{
placeHolderName += "_" + IncrementStep.ToString(CultureInfo.InvariantCulture);
}
return placeHolderName;
}
}
The following pipeline code is used to extract existing placeholder settings assigned to the non-dynamic placeholder name. This section of code had to be modified from what was provided on the Stackoverflow post, here is where we netted out:
/// <summary>
/// Handles changing context to the references dynamic "master" renderings settings for inserting the allowed controls for the placeholder and making it editable
/// </summary>
public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
{
//text that ends in a GUID
private const string DynamicKeyRegex = @"(.+)_[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}";
public new void Process(GetPlaceholderRenderingsArgs args)
{
Assert.IsNotNull(args, "args");
string placeholderKey = args.PlaceholderKey;
var regex = new Regex(DynamicKeyRegex);
Match match = regex.Match(placeholderKey);
if (match.Success && match.Groups.Count > 0)
{
placeholderKey = match.Groups[1].Value;
}
else
{
return;
}
// Same as Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings but with fake placeholderKey
Item placeholderItem = null;
if (ID.IsNullOrEmpty(args.DeviceId))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
else
{
using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
}
List<Item> collection = null;
if (placeholderItem != null)
{
bool flag;
args.HasPlaceholderSettings = true;
collection = GetRenderings(placeholderItem, out flag);
if (flag)
{
args.Options.ShowTree = false;
}
}
if (collection != null)
{
if (args.PlaceholderRenderings == null)
{
args.PlaceholderRenderings = new List<Item>();
}
args.PlaceholderRenderings.AddRange(collection);
}
}
}
In case you are interested in what we changed, we removed the following line:
args.CustomData["allowedControlsSpecified"] = true;
This line was just above:
args.Options.ShowTree = false;
Finally, in an effort to keep the pretty placeholder names the following pipeline code is necessary:
/// <summary>
/// Replaces the Displayname of the Placeholder rendering with the dynamic "parent"
/// </summary>
public class GetDynamicPlaceholderChromeData : GetChromeDataProcessor
{
//text that ends in a GUID
private const string DynamicKeyRegex = @"(.+)_[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}";
public override void Process(GetChromeDataArgs args)
{
Assert.ArgumentNotNull(args, "args");
Assert.IsNotNull(args.ChromeData, "Chrome Data");
if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
{
var argument = args.CustomData["placeHolderKey"] as string;
string placeholderKey = argument;
var regex = new Regex(DynamicKeyRegex);
Match match = regex.Match(placeholderKey);
if (match.Success && match.Groups.Count > 0)
{
// Is a Dynamic Placeholder
placeholderKey = match.Groups[1].Value;
}
else
{
return;
}
// Handles replacing the displayname of the placeholder area to the master reference
if (args.Item != null)
{
string layout = ChromeContext.GetLayout(args.Item);
Item item = Client.Page.GetPlaceholderItem(placeholderKey, args.Item.Database, layout);
if (item != null)
{
args.ChromeData.DisplayName = item.DisplayName;
}
if ((item != null) && !string.IsNullOrEmpty(item.Appearance.ShortDescription))
{
args.ChromeData.ExpandedDisplayName = item.Appearance.ShortDescription;
}
}
}
}
}
I can't take credit for really any of this code as I pulled it all from the Stackoverflow post and only made some tweaks to clean it up and get it working. I unleashed ReSharper on the code as well. I only hope that this summary of how to get dynamic placeholders working with Sitecore MVC saves someone time in the future. As always, if you tackled this in a different way let me know in the comments below.
More posts in Development
Getting started with Coveo for Sitecore Free Edition
September 18, 2014
Solutions to the MaxWebConfigSizeInKB error
August 04, 2014
Inherit Sitecore insert options with this simple insert rule
April 20, 2014
Technical details for Dust Collected
April 01, 2014