True Dynamic Placeholders in MVC

A common topic I hear that comes up while working with Sitecore layouts and MVC (although the problem also occurs in Web Forms) is Placeholders not allowing the content editor to select a specific placeholder to add a rendering when that rendering already exists on the page. An Example being, lets say you have modeled your layouts similar to bootstrap. You’d like to have the ability to set a grid to a page, such as an 8×4 within the current row. Then you’d like to have maybe a 6×6 followed then by another 8×4. The problem with the Sitecore layout rendering engine, is that when you set an element to the placeholder “8×4.Left” for example, it doesn’t know which 8×4 to set that element to, so what would happen in this scenerio, is that you’d have the same element rendering for both 8×4’s, even though you only wanted it to show up in the first 8×4.

So for example your Device Editor (PLD) might look like this:

Layout: General

8×4 Grid – Placeholder: Page
–>Html Block – Placeholder: Grid.8×4.Left
6×6 Grid – Placeholder: Page
8×4 Grid – Placeholder: Page
–>Html Block – Placeholder: Grid.8×4.Right

What you would like is for the Html Block rendering to only show up in the area’s you’ve specified above. However the way it works out of the box with Sitecore’s Layout engine, is that the first Html Block in the example above, would be exactly where you would expect it to be.  However the second Html Block, would actually be up in the first 8×4, even though based on that order, it’s obviously you’d like to have it placed in the second 8×4 (right).

There are currently solutions out there that attempt to resolve this issue, including some of the articles below:

However these article solutions, are technically dynamic, but they require the content editor to specify a number to the placeholder name (within the Content Editor).  The problem with this solution is that it requires the content editor to keep track of the numbering.  That doesn’t seem like a horrible problem if your layout is pretty simple, but can be tedious and time consuming keeping track of the numbers, especially when you start moving the renderings around, adding renderings or removing a rendering.  Below is an example of how this would look in the PLD:

Layout: General

8×4 Grid – Placeholder: Page
–>Html Block – Placeholder: Grid.8×4.Left[0]
6×6 Grid – Placeholder: Page
8×4 Grid – Placeholder: Page
–>Html Block – Placeholder: Grid.8×4.Right[1]

By using the numbering system, you are specifically telling the rendering engine that you want to for example, set the Html Block to the second ([1]) 8×4 Grid in the PLD.

But ideally, wouldn’t it be great if you could set a rendering and only need to specify it’s placeholder name, without the numbering and based on it’s order in the PLD it could logically figure out which 8×4 for example, you wanted to set the Html Block for?  Below is a solution I’ve created that will solve this issues.  It’s actually a pretty simple solution.  It works with MVC and I’ve tested it against 8.0 and 8.1 within Sitecore.

First you’ll need to create a new Extension Method (Helper) for Html.Sitecore().  This extension will take the name (string) you pass it and append the placeholder name with the unique Id of the current rendering.

Second you’ll need to overwrite the Pipeline responsible for Rendering items for each placeholder on the page.  Use the code below to overwrite PerformRendering.cs

Most of the magic happens within the GetRenderings control.  It will take the current placeholder name that’s being passed in.  Keep in mind this Pipeline runs when you call Sitecore().Placeholder, so the placeholder name that is passed in, will include the placeholder name, plus the unique id of the current rendering.  Then the logic will get all the renderings for the current page (args.PageContext.PageDefinition.Renderings) and will start traversing the enumerable List of renderings.  It will not add items to be returned until it finds the rendering that matches the Unique ID of the placeholder name that you are passing in.  Once it matches the unique ID, it will get the current rendering’s RenderingItemPath, which is an ID that is common across all renderings of that same type.  It will then only add to childRenderings, the items that match the placeholder name (minus the unique id that was passed in).  It will run this logic until it finds another rendering that has the same RenderingItemPath that matches the original rendering or it runs out of renderings (whichever happens first).

Lastly, you need to make sure you patch the Sitecore.config (8.1) or Web.config (8.0 and below).  You should be able to follow the example below to do this:

That should be it.  You can now specify placeholder names in your code, by calling the extension method we created in the first code example.  So in your General Layout, you could create one by called @Html.Sitecore().DynamicPlaceholder(“Page”).  And then in your Sitecore PLD, all you need to do is ensure you have the correct Placeholder name specified and that you have the renderings in the correct order.

Also currently there is one limitation with the current implementation, it uses a string split on the pipe character (‘|’), so if you use pipes in your placeholder names, you could experience problems.  I plan to fix this limitation in future iterations of this feature.

Happy Coding, feel free to comment if you have any questions.

  • Kevin Becker

    This isn’t working for me. I’m pretty sure I got everything in place, but it doesn’t recognize the HelperExtension. I’m getting this error message:

    ‘SitecoreHelper’ does not contain a definition for ‘DynamicPlaceholder’ and no extension method ‘DynamicPlaceholder’ accepting a first argument of type ‘SitecoreHelper’ could be found (are you missing a using directive or an assembly reference?)

    Does the class or namespace matter?

    • Administrator

      Hi Kevin, so I think the issue you are seeing is that you need to include a @using reference at the top of your view. So if you have the view “ArticleListings.cshtml” you would have in the files contents:

      @using Site.Web.Helpers

      @model Article

      Some Article

      @Html.Siteocre().DynamicPlaceholder(“PlaceholderKey”)

      You could also add to the Web.config in your Views folder a reference to the Site.Web.Helpers and it should always be included. Did that help?

  • Dylan Young

    Hi Kevin, so I think the issue you are seeing is that you need to include a @using reference at the top of your view. So if you have the view “ArticleListings.cshtml” you would have in the files contents:

    @using Site.Web.Helpers

    @model Article

    Some Article
    @Html.Siteocre().DynamicPlaceholder(“PlaceholderKey”)

    You could also add to the Web.config in your Views folder a reference to the Site.Web.Helpers and it should always be included. Did that help?

    • Kevin Becker

      Yes, that helped. But now I’m getting another error. I did Part 2 as I’m using Experience Editor. It was working, but today it’s not.

      On line 43 of InsertRendering.cs the last arg for InsertRenderingAt is passing index 1 of the arrKey array. The problem is there is only 1 element in the array. This is happening while trying to render a standard Placeholder, not a Dynamic one.

      I’m running Sitecore 8.2, if that matters.

    • Kevin Becker

      Well, I’m not sure why, but I stepped through the code and was watching everything and it finally just worked. No idea why that happens.

    • Dylan Young

      Hi Kevin. Not sure why it would suddenly start working. Maybe a caching related issue. I have not tested this against 8.2, only 8.1 update 2, but I don’t see any reason why it might have a problem. I’m hoping to upload this to the Sitecore Marketplace soon, and I’ll do further testing in other versions such as 8.0 versions and 8.2 Initial Release. Let me know if you find any other issues. I am happy to address them and look into anything you come up with.