2

Background:

Sometimes we get invalid pointers to renderings in the diffs stored in the rendering fields in Sitecore. (_Final Renderings in our case but maybe it appears in in the shared _Renderings too)

For example when having some renderings in the standard values and then edit some pages. Later removing the renderings in the standard values. If you combine this with synchronizing templates between development and production environments (for example with unicorn) you get after="non-existing-id" and other weird stuff in your delta values in _Final Renderings.

Updates from Sitecore says this should not occur... but you still get some weird values at some points. So...

This combined with the problem that Sitecore 8.someversion causes the page to totally crash in both Normal mode (visitors) and in Experience editor mode. With the not-saying-so-much:

Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.Mvc
   at Sitecore.Mvc.Pipelines.Response.RenderPlaceholder.PerformRendering.CreateCyclePreventer(String placeholderName, Rendering rendering)
   at Sitecore.Mvc.Pipelines.Response.RenderPlaceholder.PerformRendering.Render(String placeholderName, TextWriter writer, RenderPlaceholderArgs args)
   ...

Findings:

The problem can be "manually" fixed in content editor

  • Open an item with such a problem in Content Editor
  • Select Presentation and click Details it opens the dialog
  • Click OK without changing anything

The values in the _Final Renderings fields are now slightly changed (still containing a diff, but "fixed") and the page may be edited i Experience Editor again.

Question:

Can we do the same work as the Ok-press on the dialog programmatically? I guess yes, but how?

(I plan to use it in a loop to fix items, but I do know how to loop items, that is not the problem in this question.)

Solution starters:

I guess we should read the existing Standard Values shared _Renderings field, the items _Renderings and _Final Renderings field. I guess into LayoutDefinition-objects.

Combine them somehow and then get the correct diffs back and write them to the field. Maybe using XmlDeltas that have been mentioned on some pages.

What I mainly need help on is how to combine them and get the correct diffs to write to _Renderings and _Final Renderings on the item.

References

Some pages i have found so far (to pages describing parts of it or related stuff):

Update:

The same crash as we get too:

  • Or.... is it as simple as just calling the correct getter and setter for the Final Renderings too? Hmmm.... – Erik Melkersson Oct 19 '18 at 12:04
1

Is was as easy as:

    public string CheckRefs(Item item)
    {
        var finalLayoutField = item.Fields[Sitecore.FieldIDs.FinalLayoutField];
        string finalXml = LayoutField.GetFieldValue(finalLayoutField);
        var finalDetails = Sitecore.Layouts.LayoutDefinition.Parse(finalXml);
        string newFinalXml = finalDetails.ToXml();
        if (newFinalXml != finalXml)
        {
            item.Editing.BeginEdit();
            LayoutField.SetFieldValue(finalLayoutField, newFinalXml);
            item.Editing.EndEdit();
            return "modified";
        }
        return "no changes";

    }

The solution was in the links I found.

I can also confirm that this make the page working for end users and editable in experience editor again.

Note

As there may have been references to renderings that no longer exist, the order of some components may have changed. But that information was lost anyway, unless you could get the rendering location back from source control and try more advance methods of ordering renderings.

Extra

If you want a more complete example you should have a

foreach (Language lang in item.Languages)
{
        Item itemLang = item.Database.GetItem(item.ID, lang);
        ...
}

somewhere too.

Reference

https://doc.sitecore.net/sitecore_experience_platform/developing/developing_with_sitecore/versioned_layouts/the_versioned_layout_api_changes

| improve this answer | |
0

I'm not sure how exactly to do the "open and save" process you're looking for, but another way to accomplish this would be to simply remove any renderings from the __Renderings and __Final Renderings fields where the ID of the rendering does not exist in Sitecore.

Something like (you'd need to modify this to do the FieldIDs.LayoutField, also):

string finalLayoutFieldValue = theItem[FieldIDs.FinalLayoutField];
if (!string.IsNullOrEmpty(finalLayoutFieldValue))
{
    XNamespace sNamespace = XNamespace.Get("s");
    XElement rootElement = XElement.Parse(finalLayoutFieldValue);
    IEnumerable<XElement> renderingElements = rootElement.Element("d")?.Elements("r");

    IEnumerable<XElement> renderingsToRemove = renderingElements.Where(rendering => rendering.Attribute(sNamespace + "id") != null && Sitecore.Context.Database.GetItem(rendering.Attribute(sNamespace + "id").Value) == null);

    if (renderingsToRemove.Any()) 
    {
        foreach (XElement renderingToRemove in renderingsToRemove)
        {
            renderingToRemove.Remove();
        }

        var finalLayoutField = (LayoutField)theItem.Fields[FieldIDs.FinalLayoutField];
        theItem.Editing.BeginEdit();
        finalLayoutField.Value = rootElement.ToString(SaveOptions.DisableFormatting);
        theItem.Editing.EndEdit();
    }
}
| improve this answer | |
  • Ah, that could be a problem too... true. I was only more thinking of renderings that should remain, but that has a reference saying that should be placed after another rendering that does no longer exist. – Erik Melkersson Oct 19 '18 at 11:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.