Showing posts with label .NET 10. Show all posts
Showing posts with label .NET 10. Show all posts

Sunday, 25 January 2026

Rendering Blazor components using HtmlRenderer

Rendering Blazor Components Dynamically with HtmlRenderer in .NET 8

πŸš€ 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

Your Blazor Component (e.g., <Alert>) Generic Renderer (TComponent + parameters) HtmlRenderer .NET 8 server-side renderer HTML Output (string / MarkupString)

πŸ“¦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

HtmlRenderer API .NET 8 MVC Razor Pages Minimal APIs Background Jobs (emails, PDFs, reports) Static HTML Generation

🏁 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