Skip to content

Overwriting RenderTreeFrame within RenderTreeBuilder for a Markdown blazor component #36976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Hecatron opened this issue Sep 25, 2021 · 3 comments
Labels
area-blazor Includes: Blazor, Razor Components

Comments

@Hecatron
Copy link

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>
@Hecatron
Copy link
Author

One suggestion would be an api function on the RenderTreeBuilder to Add in a existing RenderTreeFrame from a different collection, alongside all the other Add function calls.

@mkArtakMSFT mkArtakMSFT added the area-blazor Includes: Blazor, Razor Components label Sep 26, 2021
@mkArtakMSFT
Copy link
Contributor

Thanks for contacting us. RenderTreeBuilder APIs are not meant to be consumed by public. You can use these APIs at your own risk, but we don't plan to add new features / APIs to those based on external asks for this area.

@Hecatron
Copy link
Author

I think part of the question was if there will be a supported api some place else at some point to do this sort of thing? (or the equivalent) even if it's not within RenderTreeBuilder

@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

2 participants