Manual Rendering in Blazor

Manual Rendering in Blazor

Lately, I’ve been researching how rendering works, in Blazor. For anyone who hasn’t fallen down that particular rabbit hole, yet, let me provide a brief overview.

In Blazor, Razor components are typically built, by a developer, using HTML like this:

@page "/"

<MudTabs>
    <MudTabPanel Text="Tab One">
        <MudField>Content One</MudField>
    </MudTabPanel>
</MudTabs>

What may not be obvious is, that HTML is then re-compiled, by Blazor, and turned into code-behind C# that looks more like this:

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenComponent<MudTabs>(0);
        builder.AddAttribute(1, "ChildContent", new RenderFragment(childBuilder =>
        {
            childBuilder.OpenComponent<MudTabPanel>(2);
            childBuilder.AddAttribute(3, "Text", "Tab One");

            childBuilder.AddAttribute(4, "ChildContent", new 
            RenderFragment(childBuilder2 =>
            {
                childBuilder2.OpenComponent<MudField>(5);
                childBuilder2.AddAttribute(6, "ChildContent", new 
                RenderFragment(childBuilder3 =>
                {
                    childBuilder3.AddContent(7, "Content One");
                }));
                childBuilder2.CloseComponent();
            }));
            childBuilder.CloseComponent();
        }));
        builder.CloseComponent();

        base.BuildRenderTree(builder);
    }

The good news is, unless you’re trying to do some advanced rendering, you don’t have to worry about any of this.

Wait, in that case, why am I bringing it up?

Well, because I am currently researching how to dynamically produce HTML based forms, in Blazor, using nothing more than a POCO reference. And that, my friends, is exactly the sort of scenario that requires me to worry about the lower level code-behind that Blazor produces, for rendering.

Lucky me, right?

Good I actually enjoy this sort of thing, or this might suck.

While doing my research I learned something, the hard way. It’s essentially this. See the code above? See how the index parameter to all those method calls is hard coded? For instance:

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    builder.OpenComponent<MudTabs>(0); // <-- this index, here
    // code removed, for clarity.
    builder.CloseComponent();

    base.BuildRenderTree(builder);
}

See how the parameter to OpenComponent is specifically set to zero? That’s important for a couple of reasons.

Firstly, Blazor expects indexes to start at zero and increment for everything added to the rendering tree. In other words, every time we add a component, or whatever, we need to increment that index.

Secondly, the indexes should all be unique, for each call. So, no fair using the same number over and over again.

If you’re like me, that leads you to believe this might be a good solution:

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    var index = 0;
    builder.OpenComponent<MudTabs>(index++); // <-- dynamic index.
    // code removed, for clarity.
    builder.CloseComponent();

    base.BuildRenderTree(builder);
}

This way, the indexes start at zero and increment every time we use one. Perfect, right?

Well, yes and no.

It turns out, at runtime, Blazor might choose to render the tree, or parts of the tree, more than once. I know, that seems counter intuitive. It does happen though. In fact, the code above might get called every time Blazor thinks anything has changed, in any portion of the render tree.

So, if you’re not careful how you go about generating your indexes, you could easily kick off an endless loop of rendering that will surely crash your website.

How? Well, admittedly, a real code sample that produces said effect would likely be long enough to make us all go cross-eyed. I’ll spare everyone that ordeal. I can, however, describe what happened to me.

My code uses reflection to iterate over all the public properties on a POCO type. For each property found, I generate a HTML control. The end result is a Blazor form dynamically generated from a POCO type.

The problem comes when I start rendering child content for one (or or more) of those HTML controls. For instance:

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    var index = 0;
    foreach (var prop in props) // <-- pretend we initialized props
    {
       builder.OpenComponent<MudTabPanel>(index++); // <-- dynamic index.
       builder.AddAttribute(index++, "ChildContent", new RenderFragment(builder2 =>
       {
           // code removed, for clarity.
       }));       
       builder.CloseComponent();
    }    
    base.BuildRenderTree(builder);
}

Pretend, for the sake of brevity, that in the code sample above, I initialized the props variable, using reflection, to find one or more properties on a POCO type. I won’t show that code because it would just get in the way of the example.

So, in this example, we’re iterating over all the properties on a POCO, and adding a MudTabPanel control, for each one, to our form. This might happen if, for instance, we wanted all the properties on our POCO type to appear in a separate tab page. But, notice that we’re also adding ChildContent for each the MudTabPanel control. So, for instance, assume that each tab panel contains any number of controls and that the end result will be a form with lots of controls, all organized neatly into individual tab pages.

The problem is this: if Blazor decides it needs to re-render part (or all) of the page, then the index variable will continue to increment, each time the code is called, and Blazor will continue to think it’s found new content to render, because of the new index values. That will kick off another round of rendering, which, thanks to our ever incrementing index, will kick off another round of rendering, which will kick off another round of rendering, which …

It doesn’t take much of that sort of thing to cause trouble.

The answer is simple, just do something like this:

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    var index = 0;
    foreach (var prop in props) 
    {
       builder.OpenComponent<MudTabPanel>(index++); 
       builder.AddAttribute(index++, "ChildContent", new RenderFragment(builder2 =>
       {
           var index2 = 0; // This is the important part!
           // code removed, for clarity.
       }));       ​
      ​builder.CloseComponent();
   ​}    
   ​base.BuildRenderTree(builder);
}

That’s right, just start the index over for any child content. I know, it’s so simple. It’s obvious, really, except that nobody ever talks about it. So, I don’t know, maybe it’s not so obvious.

I don’t want to admit how long I spent chasing this very problem, before I realized what was going on.

I figure if it bit me, it might bite you, as well. I hope this prevents everyone else from losing time on their next project.

Photo by Artem Kniaz on Unsplash