Skip to content

Overwriting RenderTreeFrame within RenderTreeBuilder for a Markdown blazor component #36976

Closed
@Hecatron

Description

@Hecatron

Hi,
I've recently been looking at using blazor to render Markdown content as part of a blog.
Using the ChildContent which contains the markdown syntax and at the same time render any sub components inside the markdown.

It turns out this is possible (and pretty awesome)
But the code is a little sketchy in certain places

The question I've got is, is there a better way to do this in so far as the RenderTreeBuilder Array?
Such as a function call which sets rather than Adds a frame.
That way I could just ChildContent(destbuilder);
Then override the frames I want with a set function to turn them from markdown syntax into html.

I came across this but it didn't seem of much help
#12415

Perhaps there's a different / better way I should be doing this, or perhaps some API method I'm not familiar with.

Markdown.cs

using Markdig;
using Ganss.XSS;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;

namespace BlogFace.Components.Markdown {

    /// <summary> Blazor component for rendering markdown using Markdig. </summary>
    public class Markdown : ComponentBase {

        /// <summary> The Markdig pipeline. </summary>
        /// <value> The Markdig pipeline. </value>
        [Parameter]
        public MarkdownPipeline MdPipeLine { get; set; }

        /// <summary> The child content. </summary>
        /// <value> The child content. </value>
        [Parameter]
        public RenderFragment ChildContent { get; set; }

        /// <summary> Service used to sanitize the html. </summary>
        /// <value> The HTML sanitizer. </value>
        [Inject] 
        public IHtmlSanitizer HtmlSanitizer { get; set; }


        /// <summary> On initial Setup </summary>
        protected override void OnInitialized() {
            base.OnInitialized();
            if (MdPipeLine == null) {
                MdPipeLine = new MarkdownPipelineBuilder()
                    .UseAdvancedExtensions()
                    .Build();
            }
        }

        /// <summary> Convert markdown string to html. </summary>
        /// <param name="value"> The string value to render. </param>
        /// <returns> The string converted to html. </returns>
        protected string ConvertMarkdownToString(string value) {
            if (!string.IsNullOrWhiteSpace(value)) {
                // Convert markdown string to HTML
                var html = Markdig.Markdown.ToHtml(value, MdPipeLine);
                // Sanitize HTML before rendering
                var sanitizedHtml = HtmlSanitizer.Sanitize(html);
                // Return sanitized HTML
                return sanitizedHtml;
            }
            return "";
        }

        /// <summary> Builds the render tree. </summary>
        /// <param name="destbuilder"> The destbuilder. </param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "BL0006:Do not use RenderTree types", Justification = "<Pending>")]
        protected override void BuildRenderTree(RenderTreeBuilder destbuilder) {
            base.BuildRenderTree(destbuilder);

            // We use this to extract the text from the ChildContent so we can pass it to Markdig
            var srcbuilder = new RenderTreeBuilder();
            ChildContent(srcbuilder);

            // Get the Frames
            var frames = srcbuilder.GetFrames();
            var framearr = frames.Array;
            var framecount = frames.Count;

            // Loop over the frames rendered from ChildContent
            for (int i = 0; i < framecount; i++) {
                var frame = framearr[i];

                // Assume Markup is Markdown syntax and render it via Markdig
                if (frame.FrameType == RenderTreeFrameType.Markup) {
                    var src_content = frame.MarkupContent;
                    destbuilder.AddMarkupContent(i, ConvertMarkdownToString(src_content));

                } else {

                    // Add other sub components
                    var destframes = destbuilder.GetFrames();

                    // This part is kind of sketchy but it works
                    // Is there a better way to do this?

                    // make sure the array counter is incremented internally.
                    destbuilder.AddMarkupContent(i, "");
                    // move the reference over from the ChildContent
                    destframes.Array[i] = framearr[i];
                }
            }
        }

    }
}

An Example of usage below
Note TestComponent1 can be anything
it's just an example of embedding a blazor component in the middle of the markdown text.
Works fine with code blocks too.

Example1.razor

<Markdown>
# Test Header1

## Test Header2

This is some more content, hello world

<TestComponent1>
</TestComponent1>

# Test Header3

## Test Header4

This is some more content, hello world

</Markdown>

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-blazorIncludes: Blazor, Razor Components

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions