π Rendering Blazor Components Dynamically with HtmlRenderer in .NET 8
A practical example using a generic component renderer
Source code available here:
π https://github.com/toreaurstadboss/BlazorIntroCourse/tree/main/Components/Demos/TemplatedComponents
Blazor has always been about component‑driven UI, but until .NET 8, components were tightly coupled to the Blazor runtime — either WebAssembly or Server.
With the introduction of HtmlRenderer, that boundary disappears.
You can now render any .razor component into pure HTML on the server, without a browser, without WebAssembly, and without a running Blazor app.
This opens up a whole new world of scenarios:
- Rendering components inside MVC, Razor Pages, or Minimal APIs
- Generating HTML for emails, PDFs, reports, or static site generation
- Using Blazor components as server‑side templates
- Building dynamic component renderers that choose components at runtime
- Running components in background services or unit tests
In this post, I’ll walk through a practical example:
A generic Blazor component that uses HtmlRenderer to render any component dynamically, and a simple Bootstrap‑style Alert component to demonstrate how it works.
π― Why HtmlRenderer Matters
Here’s the short version — arguments you can inform other developers why HtmlRenderer opens up so many possibilities for using Blazor components many places:
- Render Blazor components anywhere — MVC, Razor Pages, Minimal APIs, background jobs.
- Generate static HTML — perfect for emails, PDFs, SEO, caching, and static sites.
- Use Blazor components as templates — no need for Razor Views or TagHelpers.
- Full component lifecycle — DI, parameters, cascading values, child content all work.
- No browser required — everything runs server‑side.
- Dynamic component composition — choose components at runtime using generics.
- Great for testing — render components without a browser or JS runtime.
πΌ️ Diagram: How HtmlRenderer Works
π¦Sample Blazor component used | Alert.razor — A Simple Bootstrap‑Style Alert Component
<div
class=@($"alert {(IsDismissable ? "alert-dismissible fade show" : "")} alert-{AlertType.ToString().ToLower()}")
role="alert">
@if (IsDismissable)
{
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
}
@ChildContent
</div>
@code {
[Parameter]
public required RenderFragment ChildContent { get; set; } = @<b>Default message</b>;
[Parameter]
public bool IsDismissable { get; set; }
[Parameter]
public AlertTypeEnum AlertType { get; set; } = AlertTypeEnum.Success;
}
π§© The alert type enum | AlertTypeEnum.cs
namespace DependencyInjectionDemo.Components.Demos.TemplatedComponents
{
public enum AlertTypeEnum
{
Primary,
Secondary,
Success,
Danger,
Warning,
Info,
Light,
Dark
}
}
π§ The Generic HTML Renderer Component | GenericHtmlRenderer.razor
@((MarkupString)RenderedHtml)
@typeparam TComponent
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public Dictionary<string, object?> Parameters { get; set; } = new();
[Inject] IServiceProvider ServiceProvider { get; set; } = default!;
[Inject] ILoggerFactory LoggerFactory { get; set; } = default!;
private string RenderedHtml { get; set; } = string.Empty;
protected override async Task OnInitializedAsync()
{
if (ChildContent != null)
{
Parameters["ChildContent"] = ChildContent;
}
RenderedHtml = await RenderComponentAsync(Parameters);
}
private async Task<string> RenderComponentAsync(Dictionary<string, object?> parameters)
{
if (!typeof(IComponent).IsAssignableFrom(typeof(TComponent)))
{
throw new InvalidOperationException($"{typeof(TComponent).Name} is not a valid Blazor component.");
}
using var htmlRenderer = new HtmlRenderer(ServiceProvider, LoggerFactory);
var parameterView = ParameterView.FromDictionary(parameters);
var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
var result = await htmlRenderer.RenderComponentAsync(typeof(TComponent), parameterView);
return result.ToHtmlString();
});
return html;
}
}
π§ͺ Demo Page — Rendering Alerts Dynamically | GenericHtmlRendererDemo.razor
@page "/GenericHtmlRendererDemo"
@using BlazorIntroCourse.Components.Demos.TemplatedComponents
<h3>GenericHtmlRendererDemo - Dynamic Alert rendering</h3>
<h6>Here the parameters are set in code</h6>
<GenericHtmlRenderer TComponent="Alert"
ChildContent="alertContent"
Parameters="alternateAlertParameters" />
<h6>Here we use the ChildContent element to enter in the html</h6>
<GenericHtmlRenderer TComponent="Alert"
Parameters="alertParameters">
<ChildContent>
<text>This is the second alert</text>
</ChildContent>
</GenericHtmlRenderer>
<h6>Here we use inline object initializer for parameters</h6>
<GenericHtmlRenderer TComponent="Alert"
Parameters="@(new Dictionary<string, object>{
[ "AlertType" ] = AlertTypeEnum.Danger,
[ "IsDismissable" ] = true
})">
<ChildContent>
<text>This is the third alert</text>
</ChildContent>
</GenericHtmlRenderer>
@code {
private RenderFragment alertContent = @<text>This is the first Alert</text>;
Dictionary<string, object?> alertParameters = new()
{
{ "AlertType", AlertTypeEnum.Info},
{ "IsDismissable", true }
};
Dictionary<string, object?> alternateAlertParameters = new()
{
{ "AlertType", AlertTypeEnum.Success },
{ "IsDismissable", true }
};
}
π Diagram: Where You Can Use HtmlRenderer
π Wrapping Up
HtmlRenderer in .NET 8 is one of those features that quietly unlocks a huge amount of flexibility.
It turns Blazor components into universal UI building blocks that can be rendered:
- in a browser
- on the server
- inside MVC
- inside Razor Pages
- inside Minimal APIs
- inside background services
- inside unit tests
- or even at build time
The generic component renderer shown here is a concrete example of how powerful this can be — dynamic component rendering, runtime composition, and server‑side HTML generation all in one.
If you’re curious, you can explore the full source code here: (part of a larger repo I am looking into currently as a playground for misc Blazor functionality):
π https://github.com/toreaurstadboss/BlazorIntroCourse/tree/main/Components/Demos/TemplatedComponents
