Get Sitecore placeholders and rendering hierarchy from a Sitecore item

This article explains how you can get the hierarchy of placeholders and render them to your screen for debugging or documentation purposes.

The Sitecore rendering engine allows you to place sublayouts inside placeholders. These sublayouts can contain more placeholders, thus creating a hierarchy of placeholders inside a page.
This hierarchy can get pretty complex. So the following code demonstrates how you can read the layout from an item, the placeholders from a layout or sublayout, which renderings will be rendered where and how to write it all to an XML document that will show you which sublayout contains which placeholders and which renderings is rendered where.

STEP 1: THE BASIC METHODS:

First i give you the basic methods for getting layout, placeholder and rendering information. These methods can be copied into your own project directly:

  /// <summary>
  /// Return all placeholder keys defined on one item
  /// </summary>
  private IEnumerable<string> GetPlaceholderKeys(Sitecore.Data.Items.Item item)
  {
    List<string> uniquePlaceholderKeys = new List<string>();
    Sitecore.Layouts.RenderingReference[] renderings = GetRenderingReferences(item, "default");
    foreach (var rendering in renderings)
    {
      if (!uniquePlaceholderKeys.Contains(rendering.Placeholder))
        uniquePlaceholderKeys.Add(rendering.Placeholder);
    }
    return uniquePlaceholderKeys;
  }

  /// <summary>
  /// Search for all placeholder controls in a specific file and return all the placeholder keys
  /// </summary>
  /// <param name="relativePath"></param>
  /// <returns></returns>
  private IEnumerable<string> GetPlaceholderKeysFromFile(string relativePath)
  {
    string text = System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath(relativePath));
    string pattern = "<sc:Placeholder (.*?) />";
    MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      string keyPattern = @"Key=""(.*?)""";
      Match keyMatch = Regex.Match(match.Value, keyPattern, RegexOptions.IgnoreCase);
      yield return keyMatch.Value.Replace("Key=", "").Replace("key=", "").Replace("\"", "");
    }
  }

  /// <summary>
  /// Return all renderings to be rendered in a specific placeholder on the "default" device
  /// </summary>
  private IEnumerable<Sitecore.Data.Items.RenderingItem> GetRenderings(string placeholderKey, Sitecore.Data.Items.Item item)
  {
    Sitecore.Layouts.RenderingReference[] renderings = GetRenderingReferences(item, "default");
    foreach (var rendering in renderings)
    {
      if (rendering.Placeholder == placeholderKey)
      {
        yield return rendering.RenderingItem;
      }
    }
  }

  /// <summary>
  /// Return all renderings from an item defined on a device
  /// </summary>
  private Sitecore.Layouts.RenderingReference[] GetRenderingReferences(Sitecore.Data.Items.Item item, string deviceName)
  {
    Sitecore.Data.Fields.LayoutField layoutField = item.Fields["__renderings"];
    Sitecore.Layouts.RenderingReference[] renderings = layoutField.GetReferences(GetDeviceItem(item.Database, deviceName));
    return renderings;
  }

  /// <summary>
  /// Get the layout from an item defined on a device
  /// </summary>
  private Sitecore.Data.Items.LayoutItem GetLayout(Sitecore.Data.Items.Item item, string deviceName)
  {
    Sitecore.Data.Fields.LayoutField layoutField = item.Fields["__renderings"];
    return new Sitecore.Data.Items.LayoutItem(item.Database.GetItem(layoutField.GetLayoutID(GetDeviceItem(item.Database, deviceName))));
  }

  /// <summary>
  /// Convert a Sitecore item to a Sublayout item
  /// </summary>
  private Sitecore.Data.Items.SublayoutItem GetSublayout(Sitecore.Data.Items.Item item)
  {
    return new Sitecore.Data.Items.SublayoutItem(item);
  }

  /// <summary>
  /// Get the device item from a device name
  /// </summary>
  private Sitecore.Data.Items.DeviceItem GetDeviceItem(Sitecore.Data.Database db, string deviceName)
  {
    return db.Resources.Devices.GetAll().Where(d => d.Name.ToLower() == deviceName.ToLower()).First();
  }

  /// <summary>
  /// Get all placeholder settings that defines the specified placeholderKey
  /// </summary>
  private IEnumerable<Sitecore.Data.Items.Item> GetPlaceholderSettings(Sitecore.Data.Database db, string placeholderKey)
  {
    Sitecore.Data.Items.Item root = db.GetItem("/sitecore/layout/Placeholder Settings");
    foreach (Sitecore.Data.Items.Item descendant in root.Axes.GetDescendants())
    {
      if (descendant.Template.Key != "placeholder")
        continue;
      if (descendant["Placeholder Key"].ToLowerInvariant() == placeholderKey.ToLowerInvariant())
        yield return descendant;
    }
  }

Each of these methods can be used individually, but will reference each other some times.

To get all placeholders (i.e. the placeholder “keys” which is the unique definition of a placeholder) that contain renderings, call GetPlaceHolderKeys().

To get all placeholders that is defined in a file, call GetPlaceholderKeysFromFile(). This method uses a Regular Expression to extract all the <sc:Placeholder /> definitions found inside a source file.

To get all renderings that is rendered to a specific placeholder, call GetRenderings().

STEP 2: GET THE HIERACHY OF RENDERINGS:

Here is how to use the methods. These 2 functions will render an XML structure of the hierarchy of layouts, sublayouts, placeholder etc. You should replace /sitecore/templates/somepage/__Standard Values with the page you wish to get the hierarchy from:

  void Page_Load(object sender, System.EventArgs e)
  {
    Sitecore.Data.Database db = Sitecore.Configuration.Factory.GetDatabase("master");
    Sitecore.Data.Items.Item item = db.GetItem("/sitecore/templates/somepage/__Standard Values");

    Response.Write(string.Format(@"<template name=""{0}"">", GetLayout(item, "default").Name));
    foreach (string s in GetPlaceholderKeysFromFile(GetLayout(item, "default").FilePath))
    {
      RenderPlaceholders(s, item);
    }
    Response.Write("</template>");
  }

  private void RenderPlaceholders(string placeholderKey, Sitecore.Data.Items.Item item)
  {
    IEnumerable<Sitecore.Data.Items.Item> placeholderSettings = GetPlaceholderSettings(item.Database, placeholderKey);
    int settingsCount = placeholderSettings.Count();
    bool editable = placeholderSettings.Any(i => i["Editable"] == "1");
    Response.Write(string.Format(@"<placeholder key=""{0}"" editable=""{1}"">", placeholderKey, settingsCount, editable));

    foreach (var rendering in GetRenderings(placeholderKey, item))
    {
      Response.Write(string.Format(@"<sublayout name=""{0}"">", rendering.Name));
      foreach (string s in GetPlaceholderKeysFromFile(GetSublayout(rendering.InnerItem).FilePath))
      {
        RenderPlaceholders(s, item);
      }
      Response.Write(@"</sublayout>");
    }
    Response.Write(@"</placeholder>");
  }

The method will output something like this:

<template name="Default">
  <placeholder key="SeoRegion" editable="0">
    <sublayout name="MetaTags"></sublayout>
    <sublayout name="DocumentDescription"></sublayout>
    <sublayout name="OpenGraph"></sublayout>
  </placeholder>
  <placeholder key="phPageHolder" editable="1">
    <sublayout name="DefaultPage">
      <placeholder key="DefaultColumn1" editable="0">
        <sublayout name="DefaultPage">
        </sublayout>
      </placeholder>
    </sublayout>
  </placeholder>
  <placeholder key="ScriptsRegion" editable="0">
    <sublayout name="GoogleAnalytics"></sublayout>
  </placeholder>
</template>

That’s it. Happy coding.

Advertisement

About briancaos

Developer at Pentia A/S since 2003. Have developed Web Applications using Sitecore Since Sitecore 4.1.
This entry was posted in c#, General .NET, Sitecore 5, Sitecore 6, Sitecore 7 and tagged , , , , , . Bookmark the permalink.

6 Responses to Get Sitecore placeholders and rendering hierarchy from a Sitecore item

  1. Russ says:

    Great blog post, thank you. I did notice one issue with the regex pattern…it will not find matches when the placeholder has an explicit close tag, like this:

    Thanks,
    Russ

    Like

  2. Russ says:

    The html in my comment got escaped, so for completion, what I’m trying to include is this HTML pseudocode:

    (open caret)sc:placeholder (attributes)(closing caret)(open caret with closing slash)sc:placeholder(closing caret). The pattern appears to only match placeholder elements that have a both the open and close tag…like this:
    (open caret)sc:placeholder (attributes) (closing cate)

    Like

  3. Pingback: Sitecore locating sublayouts on your webpage | Brian Pedersen's Sitecore and .NET Blog

  4. Rahul says:

    layoutField.GetReferences(GetDeviceItem(item.Database, deviceName)); This always returns Null to me. We have a heirarchical structure where the renderings are created in individual folders. Can you please help? Do I have to pass the device ? What is I dont pass it?

    Like

  5. Calum says:

    Great post, do you have any code that will achieve a similar result in MVC?

    Like

  6. briancaos says:

    My colleague Troels made a powershell script listing all renderings in use. It does not return the rendering hierarchy, nor does it provide a MVC version, but it might help you anyway:
    https://codebuildplay.wordpress.com/2016/11/15/sitecore-powershell-how-to-create-a-list-of-renderings-in-use/

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.