Cascading MVC renderings in Sitecore 7

I will make a short break in my series about YouTube Video Picker. This one is worth it.

MVC Pipelines

You have probably seen Alex Shyba’s post on how to cascade renderings in Sitecore 6.5. It’s a neat solution with a little twist to the insertRenderings pipeline (called as part of renderLayout). It’s great but it doesn’t work on MVC layouts and renderings. Just go ahead and try it if you have a minute to spare. The Page Editor still uses executePageEditorAction pipeline and it still manages field values the way it used to but the page itself renders differently. In short (and I will sure write more about it later), renderLayout is not called for MVC layouts.

Please welcome Sitecore.Mvc.config and Sitecore.Mvc.ExperienceEditor.config (from App_ConfigInclude). The pipeline that we will need to tap into to cascade MVC renderings is defined as:

<mvc.getXmlBasedLayoutDefinition>
        <processor type="[...] GetXmlBasedLayoutDefinition.GetFromLayoutField [...]"/>
</mvc.getXmlBasedLayoutDefinition>

It is further extended for the Page Editor with:

<mvc.getXmlBasedLayoutDefinition>
    <processor
        type="[...] GetXmlBasedLayoutDefinition.GetPageDesigningLayout [...]" 
        patch:before="processor[1]"/>
    <processor 
        type="[...] GetXmlBasedLayoutDefinition.SetLayoutContext [...]" 
        patch:after="processor[@type='[...] GetXmlBasedLayoutDefinition.GetFromLayoutField [...]']"/>
</mvc.getXmlBasedLayoutDefinition>

The default processor gets renderings for a page from the Layout Details field. The page editor will intervene and get it from the being edited state if one exists. We need to enrich the renderings with the ones cascaded down from the item’s parent so our trick will be to:

<mvc.getXmlBasedLayoutDefinition>
    <processor 
        patch:before="*[last()]"
        type="[...] AddCascadedRenderings [...]"/>
</mvc.getXmlBasedLayoutDefinition>

Almost there it feels like.

Layout Definition and Layout Deltas

The new MVC pipelines are lean, concise, terse. They don’t carry args.Renderings anymore. They stick to bare metal – XElement of the item’s Layout Definition. In a simple form it’s:

XDocument.Parse(item[FieldIDs.LayoutField]).Root;

It’s not that simple though. Since layout deltas were introduced in 6.4 an item’s __Renderings will have either a layout delta (in case presentation details were defined on the item’s template’s standard values) or the actual layout definition. Sitecore handles the split and merge for you. A much closer version is this then:

XDocument.Parse(LayoutField.GetFieldValue(item.Fields[FieldIDs.LayoutField])).Root

XmlDeltas is worth looking into. That’s what LayoutField.GetFieldValue() calls behind the scenes. And you may also want to read this nice blog post about how you can leverage layout deltas to build complex layout hierarchies.

Cascading Renderings

Now we’re ready to cascade some renderings. Firs off, a template that inherits from Standard Rendering Parameters. With its help we can now tell our renderings to cascade:

Cascade

And a new AddCascadedRenderings processor into mvc.getXmlBasedLayoutDefinition:

Disclaimer: The code is experimental. It works but has certain limitations that I will get into and it has not been tested on real use cases.
Use at your own risk, as always.

The starting points:

public class AddCascadedRenderings : GetXmlBasedLayoutDefinitionProcessor
{
}

The pipeline handler:

public override void Process(GetXmlBasedLayoutDefinitionArgs args)
{
    if (args.Result == null) return;

    Item item = PageContext.Current.Item;
    Device device = PageContext.Current.Device;
    if (item == null || item.Parent == null || device == null) return;

    args.Result = MergeWithCascadedRenderings(args.Result, item.Parent, device);

    Log.Debug(string.Format("The result of merge is:n{0}", args.Result));
}

My first stab at cascading renderings (i.e. add rendering nodes from the item’s direct parent’s presentation details that have the Cascade checkbox checked) was to manipulate the XMLs directly. It worked and I learned quite a bit about the differences between the deltas and full layouts but I ended up with a cleaner and a better solution:

protected virtual XElement MergeWithCascadedRenderings(XElement self, Item parent, Device device)
{
    Field layoutField = parent.Fields[FieldIDs.LayoutField];

    LayoutDefinition parentParsed = LayoutDefinition.Parse(LayoutField.GetFieldValue(layoutField));
    LayoutDefinition selfParsed = LayoutDefinition.Parse(self.ToString());

    Log.Debug(string.Format("Merging n{0} nwithn {1}", selfParsed.ToXml(), parentParsed.ToXml()));

    string deviceId = string.Format("{{{0}}}", device.Id.ToString().ToUpper());

    DeviceDefinition selfDevice = selfParsed.GetDevice(deviceId);
    DeviceDefinition parentDevice = parentParsed.GetDevice(deviceId);

    if (parentDevice.Renderings == null) return self; // empty layout

    parentDevice.Renderings.Cast<RenderingDefinition>()
        .Where(r => StringUtil.ExtractParameter("Cascade", r.Parameters ?? "") == "1")
        .ForEach(selfDevice.AddRendering);

    return XDocument.Parse(selfParsed.ToXml()).Root;
}

With the two definitions we get a hold of the same device within each one and then search for all renderings in the parent’s that need to cascade. All that are found are added to the current item.

Limitations

  1. This solution only cascades down to the immediate children. You will see in your DEBUG logs that the resulting layout carries over the Cascade parameter but that rendering is truly virtual. The rendering engine runs it but Page Editor doesn’t know it’s there. Well, it does, and that’s the next limitation, but it won’t save it on the item. You can confirm in your browser’s JS console that $sc('#scLayout')‘s JSON doesn’t know about the rendering that cascaded out of the blue. Like I said in the beginning, Sitecore’s mechanisms to fetch the item’s data for edit is pretty much the same. It’s the rendering that changed with MVC
  2. While Page Editor doesn’t record the cascaded rendering on the item it has it rendered. The HTML markup is there. You can add new renderings before the cascaded one. You can’t add them after. Doing so will result in an error. I am yet to dig into it but I suspect it’s the position that confuses Sitecore. That POST to Pallete.aspx will tell Sitecore to insert the new rendering in the +1 position to what it knows exists.
  3. Page Editor will allow interacting with the cascaded layout as if it was the item’s. But it’s not.

A more robust solution would disable Page Editor interactivity for the cascaded renderings and it would also allow to cascade further down the content tree unless the inheritance stopped. Pretty much like Adobe CQ’s iparsys. But’s that’s another story. Next time I will continue the YoutTube Video Picker series.

One last thing. Enable pipelines profiling with Sitecore.PipelineProfiling.config and look at your /sitecore/admin/pipelines.aspx. And this blog post from John West makes a lot of sense wen you need to debug into a pipeline.

Pavel Veller

4 comments on Cascading MVC renderings in Sitecore 7

Ben VerheesFebruary 5, 2015 - Reply

Hi Pavel,

Did you ever find a solution to the limitation of not being able to add components after the cascaded one?

Best regards,
Ben.

Pavel VellerFebruary 5, 2015 - Reply

Hi Ben,

This blog post was just an experiment. We now have a full support for cascades in SCORE. We no longer cascade individual renderings either, we now cascade placeholders. You can see it in the recent virtual user group Brian and I did. We haven’t yet recorded a video about cascades on our SCORE YouTube Channel but I am sure it’s coming up.

We don’t allow to insert renderings after the cascade through, only before. The cascaded group of components will be basically pushed down in the receiving placeholder. It can be worked around – I think I know what it would take in our current implementation – but we decided that it wasn’t worth the effort. Cascading placeholders is more powerful than cascading individual renderings and we also have snippets (another feature of SCORE that we ought to record a short video about but you will see it demoed in that user group talk I mentioned) so I guess allowing to insert components after a cascade was not something we wanted hard enough.

Pavel

Ben VerheesFebruary 9, 2015 - Reply

Hi Pavel,

Thanks for your reply. Inserting renderings after the cascade is exactly what I do need, so guess I will have to keep on digging in to what’s happening 🙂

Best regards,
Ben.

Ryan TuckFebruary 7, 2017 - Reply

> We’re having the same issue now and came across this, did you manage to get a Solution to this at all Ben?

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back