We’re all very used to seeing Razor Components defined using Razor syntax. No surprises there, after all they’re called Razor Components. But you can also skip using Razor and build components manually using C# and Blazors RenderTreeBuilder
.
What we’ll build
Let’s start by looking at what we are going to build. We’re going to replicate the following component using the pure C# approach.
<!-- Menu.razor -->
<nav class="menu">
<ul>
<li><NavLink href="/" Match="NavLinkMatch.All">Home</NavLink></li>
<li><NavLink href="/contact">Contact</NavLink></li>
</ul>
</nav>
This is a simple menu component that will render a couple of links, nothing fancy.
Right lets get started.
Scaffolding the component
We need to create a new class, much the same as when creating a component using Razor, the name of the class is what we’ll use to reference the component in any markup. As we’re re-creating the menu component above, we’ll call our new class Menu. As this is a component, we will also need to inherit from ComponentBase
.
public class Menu : ComponentBase
{
}
That’s simple enough. The only thing we need to do now is to override the BuildRenderTree
method from the ComponentBase
class.
public class Menu : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
}
}
In terms of the basic setup that is all we need. Everything to do with defining the markup the component produces will happen inside the BuildRenderTree
method.
Defining the component
Now we’ve scaffolded the component, we need to define what it does. In order to do that we are going to use the RenderTreeBuilder
. This class contains a set of methods which we can use to define everything the component does.
We’ll start by calling the base implementation, you need to do this otherwise things go a little screwy. Then we’ll define the first line of our menu component.
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, "nav");
builder.AddAttribute(1, "class", "menu");
}
The code above translates into the following Razor markup.
<nav class="menu">
Like most of the methods we’re going to use on the RenderTreeBuilder
, the OpenElement
and AddAttribute
methods create a RenderTreeFrame
. A RenderTreeFrame
is essentially a tiny piece of the UI. It’s these building blocks Blazor uses to render the final HTML output.
OK, let’s create the rest of the instructions for our menu component up to the first link.
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, "nav");
builder.AddAttribute(1, "class", "menu");
builder.OpenElement(2, "ul");
builder.OpenElement(3, "li");
builder.OpenComponent<NavLink>(4);
builder.AddAttribute(5, "href", "/");
builder.AddAttribute(6, "Match", NavLinkMatch.All);
builder.AddAttribute(7, "ChildContent", (RenderFragment)((builder2) => {
builder2.AddContent(8, "Home");
}));
builder.CloseComponent();
builder.CloseElement();
}
The code above now translates into the following Razor markup.
<nav class="menu">
<ul>
<li>
<NavLink href="/" Match="NavLinkMatch.All">Home</NavLink>
</li>
We’ve now had to create another component, NavLink
. We do this using the OpenComponent
method using the type of the component we want. The other point of interest is the child content for the NavLink
component.
Child content is always defined as a RenderFragment
. This is just a delegate that writes it’s content to a RenderTreeBuilder
. We’re using a lambda expression to build the child content and then we’re passing it to the NavLink
component as a parameter.
Let’s go ahead and write out the rest of the instructions to complete our menu component.
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, "nav");
builder.AddAttribute(1, "class", "menu");
builder.OpenElement(2, "ul");
builder.OpenElement(3, "li");
builder.OpenComponent<NavLink>(4);
builder.AddAttribute(5, "href", "/");
builder.AddAttribute(6, "Match", NavLinkMatch.All);
builder.AddAttribute(7, "ChildContent", (RenderFragment)((builder2) => {
builder2.AddContent(8, "Home");
}));
builder.CloseComponent();
builder.CloseElement();
builder.OpenElement(9, "li");
builder.OpenComponent<NavLink>(10);
builder.AddAttribute(11, "href", "/contact");
builder.AddAttribute(12, "ChildContent", (RenderFragment)((builder2) => {
builder2.AddContent(13, "Contact");
}
));
builder.CloseComponent();
builder.CloseElement();
builder.CloseElement();
builder.CloseElement();
}
The code above now translates into the Razor markup we started with.
<nav class="menu">
<ul>
<li><NavLink href="/" Match="NavLinkMatch.All">Home</NavLink></li>
<li><NavLink href="/contact">Contact</NavLink></li>
</ul>
</nav>
We can now reference our C# only component in any razor markup just as you would any other component.
<Menu />
Or even in another C# only component.
builder.OpenComponent<Menu>(0);
builder.CloseComponent();
Sequence Number
You may have noticed that most instructions we added when creating our component had a number associated with them. These are sequence number and are extremely important to understand when building components this way.
The sequence number is used when Blazor is calculating diffs. To quote Steve Sanderson.
Unlike
.jsx
files,.razor
/.cshtml
files are always compiled. This is potentially a great advantage for.razor
, because we can use the compile step to inject information that makes things better or faster at runtime.
A key example of this are sequence numbers. These indicate to the runtime which outputs came from which distinct and ordered lines of code. The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm.
There has been a lot of misunderstanding about how these numbers should be generated. It turns out most of us in the community were getting it wrong and dynamically generating these numbers using code like this.
var index = 0;
builder.OpenElement(index++, "div");
This led to Steve creating this Gist explaining why these sequence numbers should be hard-coded. I urge everyone to have a read of that Gist before embarking on creating components manually.
This leads us to the final part of this post…
Why build a component manually?
I think my personal opinion on this is that you probably shouldn’t. Well, certainly not as a default anyway.
As you can see in the example above, building component this way is quite verbose and it’s harder to read. It’s also much easier to make a mistake, you’re responsible for ordering all the instructions correctly and closing tags correctly, if you get this wrong you won’t know until runtime, as the compiler can’t help you.
This an advanced use of Blazor and most of the time is just not necessary. Dealing with the sequence numbers alone is a maintenance nightmare. I’ve used this technique when building the Blazored Menu library. But looking at it now, I fell in the trap of auto generating sequence numbers and I could have achieved the same result using just Razor.
Summary
This has been an interesting post as my view on this approach has changed while writing it. I’m certainly not saying that this method should be avoided at all costs and using it is bad practice, but I think this is an advanced technique for niche situations.
What are your thoughts? Let me know in the comments.