Dynamic Components In Blazor

May 16, 2019

By: Shaun Walker

Blazor applications are based on components. A component is an element of UI that handles user events and defines flexible UI rendering logic. One the most significant benefits of components is that they promote a high level of code reusability. Under the covers, components are .NET classes written in the form of a Razor markup page with a .razor file extension. Razor is a syntax for combining HTML markup with C# code designed for developer productivity. Razor components are compiled into .NET assemblies that can be shared and distributed as Nuget packages. At run-time components render into an in-memory representation of the browser DOM called a "render tree" that is used to update the UI in a flexible and efficient way.

Blazor uses standard HTML tags for UI composition. HTML elements specify components, and a tag 's attributes pass values to a component 's properties. The typical way to reference a component in your code is as follows:

<Title Text="Blazor" />

This approach works well when it comes to components which are available and can be statically linked during your development process. However in a framework like Oqtane which is fully dynamic and assembles a composite UI at run-time based on configuration stored in a database, a different approach is needed.

Luckily, Blazor does support a dynamic rendering model. In fact the dynamic rendering model is actually fundamental to how Blazor operates internally; however, it is obscured from developers by an abstraction layer so many people are not aware that it even exists.

If you examine a Blazor project on the file system you can browse into the /obj folder within the Client project. Within this folder there will be subfolders for your various build configurations - most of you will have a /Debug folder and some may have a /Wasm folder if you are using Oqtane in the client-side mode. Within these folders you can browse into the framework folder ( ie. netstandard2.0 ) and then choose the /Razor subfolder. This folder contains auto-generated C# class representations ( *.g.cs ) of all of your .razor component files from your main project.

Let's take a look at the contents of one of these files for a moment. For this example we will use the classic "Counter" component and we will focus on the dynamic aspects. Specifically, look at the call to the BuildRenderTree method and the associated RenderTreeBuilder parameter. The RenderTreeBuilder class is used for dynamically generating content and it contains methods to open elements, add attributes, add content, add components, etc... This is ultimately how Blazor processes your component and renders output.

#pragma checksum "Counter.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d4fabfd6e6d002e957b53fad126e1419708789b9"
// <auto-generated/>
#pragma warning disable 1591
namespace WebApplication1.Pages
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using System.Net.Http;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Layouts;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.JSInterop;
using WebApplication5;
using WebApplication5.Shared;
[Microsoft.AspNetCore.Components.Layouts.LayoutAttribute(typeof(MainLayout))]
[Microsoft.AspNetCore.Components.RouteAttribute("/counter")]
public class Counter : Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, "<h1>Counter</h1>\r\n\r\n");
builder.OpenElement(1, "p");
builder.AddContent(2, "Current count: ");
builder.AddContent(3, currentCount);
builder.CloseElement();
builder.AddMarkupContent(4, "\r\n\r\n");
builder.OpenElement(5, "button");
builder.AddAttribute(6, "class", "btn btn-primary");
builder.AddAttribute(7, "onclick", Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.UIMouseEventArgs>(this, IncrementCount));
builder.AddContent(8, "Click me");
builder.CloseElement();
}
#pragma warning restore 1998
#line 9 "Counter.razor"

int currentCount = 0;

void IncrementCount()
{
currentCount++;
}

#line default
#line hidden
}
}

#pragma warning restore 1591

Microsoft has suggested that RenderTreeBuilder is a primitive rendering aspect of Blazor and that developers should focus on using the higher level abstraction. However, RenderTreeBuilder is available for anyone to use and it can be utilized in your .razor components to satisfy the need for dynamic rendering which we described earlier.

In Oqtane we take advantage of the RenderTreeBuilder primitives to dynamically inject components into the rendering pipeline based on database configuration. The basic pattern we use is as follows:

@DynamicComponent

@functions {
RenderFragment DynamicComponent { get; set; }

protected override void OnInit()
{
DynamicComponent = builder =>
{
Type componentType = Type.GetType(typeName);
if (componentType != null)
{
builder.OpenComponent(0, componentType);
builder.CloseComponent();
}
else
{
// component does not exist with typename specified
}
};
}

Basically the code declares a RenderFragment, constructs a Type based on a fully qualified type name stored as a string ( ie. "Oqtane.Client.Modules.Counter.Index, Oqtane.Client" ), and then uses the RenderTreeBuilder primitives to dynamically create the component. In our case the type name value comes from the database. Note that if you want to use a more static approach and your component is available within your project, it is also possible to call RenderTreeBuilder and pass a static reference:

builder.OpenComponent(0, typeof(Counter));

You may also have an additional requirement to pass parameters to the component which you are dynamically injecting. To accomplish this you can use the AddAttribute method in the RenderTreeBuilder class:

builder.OpenComponent(0, componentType);
builder.AddAttribute(1, "Parameter1", value);
builder.CloseComponent();

The Blazor component model is very powerful. It provides the ability to inject components both statically and dynamically. If you would like to experiment with a fully functional example of dynamic component loading you should check out Oqtane, a modular application framework for Blazor.



Share Your Feedback...
Do You Want To Be Notified When Blogs Are Published?
RSS