Skip to main content
Post Undeleted by Mark Cassidy
Elaborated answer with code and explanation
Source Link
Kasper
  • 2.2k
  • 1
  • 15
  • 37

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. The blog post provides all relevant copy-paste ready code. With this approach, you can retrieve the PageSlidePageSlide item from the rendering data source, and render the item directly in your parent view:

@Html.Raw(PageSlideItem.RenderToStringRender())

His solution is to create a new temporary ItemDefinitionContextitem context, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item into a string.

Note that this approach requires the rendered itemFirst step is to havecreate a stripped down layout assigned, so that only the actual content is renderednew temporary (i.e. without html, head, body tags).ItemDefinitionContext:

public class PageRenderItemDefinitionContext
{
    public static PageRenderItemDefinitionContext Current => ContextService.Get().GetCurrent<PageRenderItemDefinitionContext>();

    public static PageRenderItemDefinitionContext CurrentOrNull => ContextService.Get().GetCurrentOrDefault<PageRenderItemDefinitionContext>();

    public PageDefinition Definition { get; private set; }
    public Item Item { get; private set; }
    public DisplayMode PageMode { get; set; }

    public PageRenderItemDefinitionContext(PageDefinition pageDefinition, Item item, DisplayMode exteriorDisplayMode)
    {
        Assert.ArgumentNotNull(pageDefinition, nameof(pageDefinition));
        Assert.ArgumentNotNull(item, nameof(item));

        Definition = pageDefinition;
        Item = item;
        PageMode = exteriorDisplayMode;
    }

    public static IDisposable Enter(PageRenderItemDefinitionContext context)
    {
        Assert.ArgumentNotNull(context, "context");
        return ContextService.Get().Push(context);
    }

}

In respect of Jeff's intellectual property, I have not copied in code examples from the blog post. Next, override the PerformRendering pipeline processor to use the new PageRenderItemDefinitionContext:

    public class PerformItemRendering : PerformRendering
{
    public static readonly string ItemRenderingKey = Guid.NewGuid().ToString();

    /// <summary>
    /// Render step, except it temporarily abandons the placeholder context to render a seperate item, after which it puts the context back
    /// </summary>
    /// <param name="placeholderName">Placeholder to render</param>
    /// <param name="writer">writer to render to</param>
    /// <param name="args"></param>
    protected override void Render(string placeholderName, TextWriter writer, RenderPlaceholderArgs args)
    {
        if (PageRenderItemDefinitionContext.CurrentOrNull != null)
            args.PageContext.PageDefinition = PageRenderItemDefinitionContext.Current.Definition;

        if (placeholderName != ItemRenderingKey)
        {
            base.Render(placeholderName, writer, args);
            return;
        }

        Stack<PlaceholderContext> previousContext = new Stack<PlaceholderContext>();
        while (PlaceholderContext.CurrentOrNull != null)
        {
            previousContext.Push(PlaceholderContext.Current);
            PlaceholderContext.Exit();
        }

        try
        {
            PipelineService.Get().RunPipeline("mvc.renderRendering", new RenderRenderingArgs(args.PageContext.PageDefinition.Renderings.First(x => x.Placeholder.IsWhiteSpaceOrNull()), writer));
        }
        finally
        {
            while (PlaceholderContext.CurrentOrNull != null)
                PlaceholderContext.Exit();

            while (previousContext.Any())
            {
                PlaceholderContext.Enter(previousContext.Pop());
            }
        }
    }
}

Patch in the pipeline processor:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.renderPlaceholder>
        <processor patch:instead="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderPlaceholder.PerformRendering, Sitecore.Mvc']" type="Namespace.Pipelines.Mvc.RenderPlaceholder.PerformRendering, Assembly" />
      </mvc.renderPlaceholder>
    </pipelines>
  </sitecore>
</configuration>

Finally, wire it all up as a new ItemRenderer:

/// <summary>
/// Renders an item's layout to a string or TextWriter.
/// </summary>
public class ItemRenderer
{
    public Item Item { get; set; }

    public ItemRenderer(Item item)
    {
        Item = item;
    }

    /// <summary>
    /// Renders an item with a layout defined to a string for MVC
    /// </summary>
    /// <returns>HTML of item</returns>
    public virtual string Render()
    {
        using (TextWriter tw = new StringWriter())
        {
            Render(tw);

            return tw.ToString();
        }
    }

    /// <summary>
    /// Renders an item with a layout defined to a string for MVC
    /// </summary>
    /// <returns>HTML of item</returns>
    public virtual void Render(TextWriter writer)
    {
        var originalDisplayMode = Context.Site.DisplayMode;

        // keep a copy of the renderings we start with.
        // running the renderPlaceholder pipeline (which runs renderRendering) will overwrite these
        // and we need to set them back how they were when we're done rendering the xBlock
        var originalRenderingDefinitionContext = RenderingContext.CurrentOrNull?.PageContext?.PageDefinition;

        try
        {
            // prevents editing the snippet in context, so you cannot mistakenly change something shared by mistake
            if (Context.PageMode.IsExperienceEditorEditing || Context.PageMode.IsPreview)
            {
                Context.Site.SetDisplayMode(DisplayMode.Normal, DisplayModeDuration.Temporary);
            }

            var pageDef = new PageDefinition
            {
                Renderings = new List<Rendering>()
            };

            //Extracts the item's layout XML, then parses all of the renderings out of it.
            pageDef.Renderings.AddRange(GetRenderings(GetLayoutFromItem()));

            // Uncovers the main layout rendering
            var pageRenderingArgs = new GetPageRenderingArgs(pageDef);
            PipelineService.Get().RunPipeline("mvc.getPageRendering", pageRenderingArgs);

            //Renders all placeholders for the layout rendering, which would be the entire page
            var renderPlaceholderArgs = new RenderPlaceholderArgs(PerformItemRendering.ItemRenderingKey, writer, pageRenderingArgs.Result)
            {
                PageContext = new PageContext
                {
                    PageDefinition = pageDef
                }
            };

            using (PageRenderItemDefinitionContext.Enter(new PageRenderItemDefinitionContext(pageDef, Item, originalDisplayMode)))
            {
                PipelineService.Get().RunPipeline("mvc.renderPlaceholder", renderPlaceholderArgs);
            }
        }
        catch (Exception e)
        {
            Log.Error("There was a problem rendering an item to string", e, this);
            if (originalDisplayMode == DisplayMode.Edit || originalDisplayMode == DisplayMode.Preview)
            {
                writer.Write($"<p class=\"edit-only\">Error occurred while rendering {Item.Paths.FullPath}: {e.Message}<br>For error details, <a href=\"{LinkManager.GetItemUrl(Item)}\" onclick=\"window.open(this.href); return false;\">visit the target page</a></p>");
            }
        }
        finally
        {
            // replace the renderings in the current context with the ones that existed before we ran our sideline renderPlaceholder
            // because they have been overwritten with the xBlock's renderings at this point
            if (originalRenderingDefinitionContext != null)
            {
                RenderingContext.CurrentOrNull.PageContext.PageDefinition = originalRenderingDefinitionContext;
            }

            Context.Site.SetDisplayMode(originalDisplayMode, DisplayModeDuration.Temporary);
        }
    }

    /// <summary>
    /// Gets the layout XML from the item
    /// </summary>
    /// <returns>xml of the layout definition</returns>
    protected virtual XElement GetLayoutFromItem()
    {
        Field innerField = new LayoutField(Item).InnerField;

        if (innerField == null)
            return null;

        string fieldValue = LayoutField.GetFieldValue(innerField);

        if (fieldValue.IsWhiteSpaceOrNull())
            return null;

        return XDocument.Parse(fieldValue).Root;
    }

    /// <summary>
    /// Gets the rendering out of the xml node and injects some values in
    /// </summary>
    /// <param name="renderingNode"></param>
    /// <param name="deviceId"></param>
    /// <param name="layoutId"></param>
    /// <param name="renderingType"></param>
    /// <param name="parser"></param>
    /// <returns>MVC rendering</returns>
    protected virtual Rendering GetRendering(XElement renderingNode, Guid deviceId, Guid layoutId, string renderingType, XmlBasedRenderingParser parser)
    {
        Rendering rendering = parser.Parse(renderingNode, false);
        rendering.DeviceId = deviceId;
        rendering.LayoutId = layoutId;
        if (renderingType != null)
            rendering.RenderingType = renderingType;

        // if the xBlock is rendering in the context of another page, renderings with no data source should be repointed to the xBlock page item
        // as opposed to the context page item
        if (string.IsNullOrWhiteSpace(rendering.DataSource)) rendering.DataSource = Item.ID.ToString();

        return rendering;
    }

    /// <summary>
    /// Get all renderings out of the layout definition
    /// </summary>
    /// <param name="layoutDefinition">xml of the layout definition</param>
    /// <returns>list of renderings</returns>
    protected virtual IEnumerable<Rendering> GetRenderings(XElement layoutDefinition)
    {
        XmlBasedRenderingParser parser = MvcSettings.GetRegisteredObject<XmlBasedRenderingParser>();
        foreach (XElement xelement in layoutDefinition.Elements("d"))
        {
            Guid deviceId = xelement.GetAttributeValueOrEmpty("id").ToGuid();
            Guid layoutId = xelement.GetAttributeValueOrEmpty("l").ToGuid();

            yield return GetRendering(xelement, deviceId, layoutId, "Layout", parser);

            foreach (XElement renderingNode in xelement.Elements("r"))
                yield return GetRendering(renderingNode, deviceId, layoutId, renderingNode.Name.LocalName, parser);
        }
    }

}

Now, using the ItemRenderer, you can render the item to a string. To ease the usage, you can add an extension method to Item

/// <summary>
/// Renders an item with a layout definition to a string
/// </summary>
/// <param name="item"></param>
/// <returns>Rendered output for the item</returns>
public static string Render(this Item item)
{
    return new ItemRenderer(item).Render();
}

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

@Html.Sitecore().Placeholder("content")

To add Experience Editor support, switch on PageMode to add containing HTML-tags:

@if (Sitecore.Context.PageMode.IsExperienceEditor || Sitecore.Context.PageMode.IsPreview)
{
    <!DOCTYPE html>
    <html lang="da">
    <head>
        <!-- Header content here -->
    </head>
    <body>
        @Html.Sitecore().Placeholder("content")
    </body>
</html>
}
else
{
    @Html.Sitecore().Placeholder("content")
}

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. The blog post provides all relevant copy-paste ready code. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item in your view:

@Html.Raw(PageSlideItem.RenderToString())

His solution is to create a temporary ItemDefinitionContext, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item.

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

In respect of Jeff's intellectual property, I have not copied in code examples from the blog post.

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. The blog post provides all relevant copy-paste ready code. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item directly in your parent view:

@Html.Raw(PageSlideItem.Render())

His solution is to create a new temporary item context, extract the layout details, and utilize Sitecores standard pipelines to render the item into a string.

First step is to create a new temporary ItemDefinitionContext:

public class PageRenderItemDefinitionContext
{
    public static PageRenderItemDefinitionContext Current => ContextService.Get().GetCurrent<PageRenderItemDefinitionContext>();

    public static PageRenderItemDefinitionContext CurrentOrNull => ContextService.Get().GetCurrentOrDefault<PageRenderItemDefinitionContext>();

    public PageDefinition Definition { get; private set; }
    public Item Item { get; private set; }
    public DisplayMode PageMode { get; set; }

    public PageRenderItemDefinitionContext(PageDefinition pageDefinition, Item item, DisplayMode exteriorDisplayMode)
    {
        Assert.ArgumentNotNull(pageDefinition, nameof(pageDefinition));
        Assert.ArgumentNotNull(item, nameof(item));

        Definition = pageDefinition;
        Item = item;
        PageMode = exteriorDisplayMode;
    }

    public static IDisposable Enter(PageRenderItemDefinitionContext context)
    {
        Assert.ArgumentNotNull(context, "context");
        return ContextService.Get().Push(context);
    }

}

Next, override the PerformRendering pipeline processor to use the new PageRenderItemDefinitionContext:

    public class PerformItemRendering : PerformRendering
{
    public static readonly string ItemRenderingKey = Guid.NewGuid().ToString();

    /// <summary>
    /// Render step, except it temporarily abandons the placeholder context to render a seperate item, after which it puts the context back
    /// </summary>
    /// <param name="placeholderName">Placeholder to render</param>
    /// <param name="writer">writer to render to</param>
    /// <param name="args"></param>
    protected override void Render(string placeholderName, TextWriter writer, RenderPlaceholderArgs args)
    {
        if (PageRenderItemDefinitionContext.CurrentOrNull != null)
            args.PageContext.PageDefinition = PageRenderItemDefinitionContext.Current.Definition;

        if (placeholderName != ItemRenderingKey)
        {
            base.Render(placeholderName, writer, args);
            return;
        }

        Stack<PlaceholderContext> previousContext = new Stack<PlaceholderContext>();
        while (PlaceholderContext.CurrentOrNull != null)
        {
            previousContext.Push(PlaceholderContext.Current);
            PlaceholderContext.Exit();
        }

        try
        {
            PipelineService.Get().RunPipeline("mvc.renderRendering", new RenderRenderingArgs(args.PageContext.PageDefinition.Renderings.First(x => x.Placeholder.IsWhiteSpaceOrNull()), writer));
        }
        finally
        {
            while (PlaceholderContext.CurrentOrNull != null)
                PlaceholderContext.Exit();

            while (previousContext.Any())
            {
                PlaceholderContext.Enter(previousContext.Pop());
            }
        }
    }
}

Patch in the pipeline processor:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.renderPlaceholder>
        <processor patch:instead="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderPlaceholder.PerformRendering, Sitecore.Mvc']" type="Namespace.Pipelines.Mvc.RenderPlaceholder.PerformRendering, Assembly" />
      </mvc.renderPlaceholder>
    </pipelines>
  </sitecore>
</configuration>

Finally, wire it all up as a new ItemRenderer:

/// <summary>
/// Renders an item's layout to a string or TextWriter.
/// </summary>
public class ItemRenderer
{
    public Item Item { get; set; }

    public ItemRenderer(Item item)
    {
        Item = item;
    }

    /// <summary>
    /// Renders an item with a layout defined to a string for MVC
    /// </summary>
    /// <returns>HTML of item</returns>
    public virtual string Render()
    {
        using (TextWriter tw = new StringWriter())
        {
            Render(tw);

            return tw.ToString();
        }
    }

    /// <summary>
    /// Renders an item with a layout defined to a string for MVC
    /// </summary>
    /// <returns>HTML of item</returns>
    public virtual void Render(TextWriter writer)
    {
        var originalDisplayMode = Context.Site.DisplayMode;

        // keep a copy of the renderings we start with.
        // running the renderPlaceholder pipeline (which runs renderRendering) will overwrite these
        // and we need to set them back how they were when we're done rendering the xBlock
        var originalRenderingDefinitionContext = RenderingContext.CurrentOrNull?.PageContext?.PageDefinition;

        try
        {
            // prevents editing the snippet in context, so you cannot mistakenly change something shared by mistake
            if (Context.PageMode.IsExperienceEditorEditing || Context.PageMode.IsPreview)
            {
                Context.Site.SetDisplayMode(DisplayMode.Normal, DisplayModeDuration.Temporary);
            }

            var pageDef = new PageDefinition
            {
                Renderings = new List<Rendering>()
            };

            //Extracts the item's layout XML, then parses all of the renderings out of it.
            pageDef.Renderings.AddRange(GetRenderings(GetLayoutFromItem()));

            // Uncovers the main layout rendering
            var pageRenderingArgs = new GetPageRenderingArgs(pageDef);
            PipelineService.Get().RunPipeline("mvc.getPageRendering", pageRenderingArgs);

            //Renders all placeholders for the layout rendering, which would be the entire page
            var renderPlaceholderArgs = new RenderPlaceholderArgs(PerformItemRendering.ItemRenderingKey, writer, pageRenderingArgs.Result)
            {
                PageContext = new PageContext
                {
                    PageDefinition = pageDef
                }
            };

            using (PageRenderItemDefinitionContext.Enter(new PageRenderItemDefinitionContext(pageDef, Item, originalDisplayMode)))
            {
                PipelineService.Get().RunPipeline("mvc.renderPlaceholder", renderPlaceholderArgs);
            }
        }
        catch (Exception e)
        {
            Log.Error("There was a problem rendering an item to string", e, this);
            if (originalDisplayMode == DisplayMode.Edit || originalDisplayMode == DisplayMode.Preview)
            {
                writer.Write($"<p class=\"edit-only\">Error occurred while rendering {Item.Paths.FullPath}: {e.Message}<br>For error details, <a href=\"{LinkManager.GetItemUrl(Item)}\" onclick=\"window.open(this.href); return false;\">visit the target page</a></p>");
            }
        }
        finally
        {
            // replace the renderings in the current context with the ones that existed before we ran our sideline renderPlaceholder
            // because they have been overwritten with the xBlock's renderings at this point
            if (originalRenderingDefinitionContext != null)
            {
                RenderingContext.CurrentOrNull.PageContext.PageDefinition = originalRenderingDefinitionContext;
            }

            Context.Site.SetDisplayMode(originalDisplayMode, DisplayModeDuration.Temporary);
        }
    }

    /// <summary>
    /// Gets the layout XML from the item
    /// </summary>
    /// <returns>xml of the layout definition</returns>
    protected virtual XElement GetLayoutFromItem()
    {
        Field innerField = new LayoutField(Item).InnerField;

        if (innerField == null)
            return null;

        string fieldValue = LayoutField.GetFieldValue(innerField);

        if (fieldValue.IsWhiteSpaceOrNull())
            return null;

        return XDocument.Parse(fieldValue).Root;
    }

    /// <summary>
    /// Gets the rendering out of the xml node and injects some values in
    /// </summary>
    /// <param name="renderingNode"></param>
    /// <param name="deviceId"></param>
    /// <param name="layoutId"></param>
    /// <param name="renderingType"></param>
    /// <param name="parser"></param>
    /// <returns>MVC rendering</returns>
    protected virtual Rendering GetRendering(XElement renderingNode, Guid deviceId, Guid layoutId, string renderingType, XmlBasedRenderingParser parser)
    {
        Rendering rendering = parser.Parse(renderingNode, false);
        rendering.DeviceId = deviceId;
        rendering.LayoutId = layoutId;
        if (renderingType != null)
            rendering.RenderingType = renderingType;

        // if the xBlock is rendering in the context of another page, renderings with no data source should be repointed to the xBlock page item
        // as opposed to the context page item
        if (string.IsNullOrWhiteSpace(rendering.DataSource)) rendering.DataSource = Item.ID.ToString();

        return rendering;
    }

    /// <summary>
    /// Get all renderings out of the layout definition
    /// </summary>
    /// <param name="layoutDefinition">xml of the layout definition</param>
    /// <returns>list of renderings</returns>
    protected virtual IEnumerable<Rendering> GetRenderings(XElement layoutDefinition)
    {
        XmlBasedRenderingParser parser = MvcSettings.GetRegisteredObject<XmlBasedRenderingParser>();
        foreach (XElement xelement in layoutDefinition.Elements("d"))
        {
            Guid deviceId = xelement.GetAttributeValueOrEmpty("id").ToGuid();
            Guid layoutId = xelement.GetAttributeValueOrEmpty("l").ToGuid();

            yield return GetRendering(xelement, deviceId, layoutId, "Layout", parser);

            foreach (XElement renderingNode in xelement.Elements("r"))
                yield return GetRendering(renderingNode, deviceId, layoutId, renderingNode.Name.LocalName, parser);
        }
    }

}

Now, using the ItemRenderer, you can render the item to a string. To ease the usage, you can add an extension method to Item

/// <summary>
/// Renders an item with a layout definition to a string
/// </summary>
/// <param name="item"></param>
/// <returns>Rendered output for the item</returns>
public static string Render(this Item item)
{
    return new ItemRenderer(item).Render();
}

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

@Html.Sitecore().Placeholder("content")

To add Experience Editor support, switch on PageMode to add containing HTML-tags:

@if (Sitecore.Context.PageMode.IsExperienceEditor || Sitecore.Context.PageMode.IsPreview)
{
    <!DOCTYPE html>
    <html lang="da">
    <head>
        <!-- Header content here -->
    </head>
    <body>
        @Html.Sitecore().Placeholder("content")
    </body>
</html>
}
else
{
    @Html.Sitecore().Placeholder("content")
}
Mod Converts to Comment
Post Deleted by Mark Cassidy
added 107 characters in body
Source Link
Kasper
  • 2.2k
  • 1
  • 15
  • 37

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. The blog post provides all relevant copy-paste ready code. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item in your view.:

@Html.Raw(PageSlideItem.RenderToString())

His solution is to create a temporary ItemDefinitionContext, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item.

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

*In respect of Jeff's intellectual property, I have not copied in code examples from the blog post. *In respect of Jeff's intellectual property, I have not copied in code examples from the blog post.

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item in your view.

His solution is to create a temporary ItemDefinitionContext, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item.

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

*In respect of Jeff's intellectual property, I have not copied in code examples from the blog post. *

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. The blog post provides all relevant copy-paste ready code. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item in your view:

@Html.Raw(PageSlideItem.RenderToString())

His solution is to create a temporary ItemDefinitionContext, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item.

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

In respect of Jeff's intellectual property, I have not copied in code examples from the blog post.

Source Link
Kasper
  • 2.2k
  • 1
  • 15
  • 37

Jeff Darchuk wrote a blog post describing how to render a Sitecore item to a string. With this approach, you can retrieve the PageSlide item from the rendering data source, and render the item in your view.

His solution is to create a temporary ItemDefinitionContext, extract the layout details and renderings, and utilize Sitecores standard pipelines to render the item.

Note that this approach requires the rendered item to have a stripped down layout assigned, so that only the actual content is rendered (i.e. without html, head, body tags).

*In respect of Jeff's intellectual property, I have not copied in code examples from the blog post. *