# ASP.NET Core Blazor Components Rendering and State Management: A Deep Dive into the Source Code

> Explore Blazor components rendering and state management in ASP.NET Core. Understand the diff-based render tree architecture, lifecycle events, and efficient DOM updates from the source code.

- Repository: [.NET Platform/aspnetcore](https://github.com/dotnet/aspnetcore)
- Tags: deep-dive
- Published: 2026-06-20

---

**Blazor components rely on a diff-based render tree architecture where `ComponentBase` orchestrates lifecycle events, `RenderTreeBuilder` constructs UI frames, and `StateHasChanged()` triggers efficient DOM updates through the `RenderTreeDiffBuilder` algorithm.**

Blazor abstracts browser rendering into C# through a sophisticated pipeline that operates identically across Server and WebAssembly hosting models. Understanding how ASP.NET Core Blazor Components rendering and state management works requires examining the framework's core implementation in the `dotnet/aspnetcore` repository. This article explores the actual source code for the render tree construction, diffing algorithm, and state notification system that powers interactive Blazor applications.

## The Component Foundation and Render Tree Structure

Blazor components are built on a thin abstraction layer centered around `ComponentBase`, which implements the core rendering contracts `IComponent`, `IHandleEvent`, and `IHandleAfterRender`. The rendering engine works with a **render tree** composed of `RenderTreeFrame` structs that describe the UI structure declaratively.

When a component renders, its `BuildRenderTree` method—either generated from Razor markup or overridden manually—populates a `RenderFragment` delegate. This delegate receives a `RenderTreeBuilder` instance that creates the actual frame sequence. As implemented in [`src/Components/Components/src/Rendering/RenderTreeBuilder.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/Rendering/RenderTreeBuilder.cs), the builder emits frames representing elements, text nodes, component references, and attributes.

## The Seven-Step Rendering Pipeline

The Blazor rendering pipeline follows a precise sequence of operations that transform component state into UI updates:

1. **Component instantiation** – The framework creates an instance of the component class, typically derived from `ComponentBase`.
2. **Initialization** – `OnInitialized` and `OnInitializedAsync` execute once after the component receives its initial parameters.
3. **Parameter assignment** – When parent components supply new parameter values, `OnParametersSet` and `OnParametersSetAsync` run to allow state recalculation.
4. **Render tree construction** – The component's `BuildRenderTree` method invokes `RenderTreeBuilder` methods to emit the frame sequence.
5. **Diff calculation** – The new render tree is compared against the previous version by `RenderTreeDiffBuilder`, located in [`src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs). Only the minimal set of changes is computed.
6. **Commit phase** – Changes are applied to the actual DOM in WebAssembly mode, or serialized and sent via SignalR to a `RemoteRenderer` in Server-Side Blazor.
7. **Post-render callbacks** – `OnAfterRender` and `OnAfterRenderAsync` fire after the UI updates complete, providing access to `firstRender` boolean to distinguish initial renders from updates.

## State Management and the StateHasChanged Mechanism

Component UI refreshes are triggered by calling `StateHasChanged()`, implemented in [`src/Components/Components/src/ComponentBase.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs). This method validates three conditions before queuing a render operation:

- No render is currently pending (checked via `_hasPendingQueuedRender`).
- The component has never rendered, `ShouldRender()` returns `true`, or a hot-reload metadata update is occurring (`_renderHandle.IsRenderingOnMetadataUpdate`).

When conditions are satisfied, the stored `_renderFragment` is passed to the `RenderHandle`, which initiates the diff-and-commit cycle. Developers can customize rendering decisions by overriding the `protected virtual bool ShouldRender()` method to return `false` when state mutations should not trigger visual updates.

## Server-Side vs. WebAssembly Rendering Models

Despite identical component code, the hosting model determines how render tree diffs reach the browser:

- **Server-Side Blazor** – Components execute on the server within `RemoteRenderer` ([`src/Components/Server/src/RemoteRenderer.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/RemoteRenderer.cs)). The diff is serialized and transmitted over a SignalR connection to a thin JavaScript client that applies changes to the browser DOM.
- **WebAssembly** – Components run directly in the browser via the .NET runtime. The `WebRenderer` ([`src/Components/Web/src/WebRenderer.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/WebRenderer.cs)) applies diffs directly to the DOM through WebAssembly interop.

The `ComponentBase` class exposes the current hosting model through `_renderHandle.RendererInfo`, allowing components to adapt behavior when necessary while maintaining the same rendering logic across both environments.

## Manual RenderTreeBuilder Usage

While Razor files generate `BuildRenderTree` automatically, developers can manually construct render trees for dynamic scenarios. The following example demonstrates imperative component construction equivalent to declarative Razor markup:

```csharp
public class CounterManual : ComponentBase
{
    private int currentCount;
    private RenderFragment _fragment;

    public CounterManual()
    {
        _fragment = builder =>
        {
            builder.AddMarkupContent(0, "<h3>Counter</h3>");
            builder.OpenElement(1, "p");
            builder.AddContent(2, $"Current count: {currentCount}");
            builder.CloseElement();
            
            builder.OpenElement(3, "button");
            builder.AddAttribute(4, "onclick", 
                EventCallback.Factory.Create(this, Increment));
            builder.AddContent(5, "Click me");
            builder.CloseElement();
        };
    }

    protected override void BuildRenderTree(RenderTreeBuilder builder) => 
        _fragment(builder);

    void Increment()
    {
        currentCount++;
        StateHasChanged();
    }
}

```

This pattern creates a `RenderFragment` that accepts a `RenderTreeBuilder` and uses sequence numbers (0, 1, 2...) to identify frames for diffing purposes. The `OpenElement` and `CloseElement` calls define HTML tags, while `AddAttribute` attaches event handlers through `EventCallback.Factory`.

## Summary

- **ComponentBase** provides the lifecycle foundation and `StateHasChanged()` implementation in [`src/Components/Components/src/ComponentBase.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs).
- **RenderTreeBuilder** constructs UI frames at [`src/Components/Components/src/Rendering/RenderTreeBuilder.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/Rendering/RenderTreeBuilder.cs) while `RenderTreeDiffBuilder` computes minimal updates.
- **StateHasChanged** queues renders through the `RenderHandle` only when no pending render exists and `ShouldRender()` returns true.
- **ShouldRender** offers a customization point to suppress unnecessary renders for performance optimization.
- **RemoteRenderer** and **WebRenderer** provide hosting-specific implementations that apply the same diff output via SignalR or direct DOM manipulation respectively.

## Frequently Asked Questions

### What triggers a Blazor component to re-render?

A component re-renders when `StateHasChanged()` is called and the internal conditions in `ComponentBase` are satisfied: no render is already queued, and either the component hasn't rendered yet, `ShouldRender()` returns `true`, or a hot-reload operation is active. Event callbacks and parameter changes automatically trigger this notification.

### How does Blazor minimize DOM updates?

Blazor minimizes DOM updates through the `RenderTreeDiffBuilder` algorithm in [`src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs). This compares the new render tree against the previous snapshot and computes only the differences. Instead of regenerating the entire UI, only changed attributes, text nodes, or element insertions/removals are transmitted and applied.

### What is the difference between OnInitialized and OnParametersSet?

`OnInitialized` and `OnInitializedAsync` execute exactly once after component creation and initial parameter assignment, making them ideal for one-time setup logic. `OnParametersSet` and `OnParametersSetAsync` run every time the parent supplies new parameter values, including the initial assignment, allowing components to react to changing input values throughout their lifecycle.

### Can you prevent a Blazor component from rendering?

Yes, by overriding `ShouldRender()` to return `false`. This protected virtual method in `ComponentBase` allows components to skip the render tree construction and diffing phases when state changes occur that don't require visual updates. This optimization prevents unnecessary CPU cycles and network traffic in Server-Side Blazor scenarios.