Blazor & Blazor Components
Comprehensive guide – hosting models, component architecture, render modes, patterns, and best practices.
Introduction
Blazor is a modern UI framework from Microsoft that allows building interactive web UIs using C# instead of JavaScript. It runs on .NET and leverages the Razor syntax for component-based development. With Blazor you can create rich single-page applications (SPA), progressive web apps (PWA), and even hybrid desktop/mobile applications – all with shared code and .NET libraries.
1. Hosting Models
Blazor offers multiple ways to run your application, each with different latency, offline support, and resource characteristics.
1.1 Blazor Server
The app executes on the server inside an ASP.NET Core process. UI updates are sent to the browser over a persistent SignalR connection. The server maintains component state per client circuit.
- Pros: Fast initial load, full .NET API access, thin client.
- Cons: Requires constant connection, higher latency per interaction, server memory scales with users.
<!-- Typical Program.cs -->
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
...
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");1.2 Blazor WebAssembly (WASM)
The app downloads the .NET runtime and assemblies to the browser and runs entirely on the client via WebAssembly. No server round-trips for UI interactions.
- Pros: Offline/PWA capable, no server dependency for interactivity, reduced latency.
- Cons: Larger download size, limited .NET APIs (sandbox), slower initial startup.
// Program.cs (WASM host)
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");1.3 Blazor Hybrid
Blazor components run inside a native application (MAUI, WPF, Windows Forms) using a WebView control. The .NET runtime runs natively on the device.
// MauiProgram.cs
builder.UseMauiApp<App>()
.ConfigureFonts(fonts => ...);
// Uses BlazorWebView1.4 .NET 8+ Blazor Web App & Render Modes
Starting with .NET 8, Blazor unifies server and WASM into a single project model (Blazor Web App) and introduces the concept of render modes. You can choose per component or per page how interactivity is delivered.
| Render Mode | Description |
|---|---|
Static SSR | Server-side rendering without interactivity (like MVC/Razor Pages). Components render on server, no SignalR/WebSocket. |
InteractiveServer | Classic Blazor Server – interactivity via SignalR circuit. |
InteractiveWebAssembly | Classic Blazor WASM – runs in browser after download. |
InteractiveAuto | Starts with InteractiveServer for speed, then switches to WASM on subsequent visits. |
@* Setting render mode on a component *@
@rendermode InteractiveServer
@* In App.razor for routes *@
<Routes @rendermode="InteractiveAuto" />Static SSR enables enhanced navigation, form handling, and streaming rendering. It's the foundation for modern Blazor United.
2. Component Fundamentals
Blazor components are .razor files that combine HTML markup and C# logic. They can accept parameters, render UI, handle events, and manage their own lifecycle.
2.1 Razor Syntax
<h1>@Title</h1>
@code {
[Parameter]
public string Title { get; set; } = "Default";
}Directives like @page, @inject, @code, @typeparam, @rendermode control routing, DI, code blocks, and behavior. The @ symbol transitions from markup to C# expressions.
2.2 Component Parameters
Properties decorated with [Parameter] become settable from parent components. Blazor supports one-way binding from parent to child.
<MyCounter CurrentCount="42" />
@code {
[Parameter]
public int CurrentCount { get; set; }
[Parameter]
public EventCallback<int> OnIncrement { get; set; }
}- Child/parent communication: Use
EventCallback<T>for child-to-parent notification. - Two-way binding: Combine a parameter and an
EventCallbacknamed by convention{ParameterName}Changed.
2.3 Cascading Values and Parameters
Pass values down the component tree implicitly without chaining through every intermediate component.
<CascadingValue Value="theme">
<ChildComponent />
</CascadingValue>
@* In child *@
@code {
[CascadingParameter]
private string Theme { get; set; }
}Can cascade by name or type. Fixed a scope with CascadingValueSource in code.
2.4 Event Handling
Handle DOM events using @on{event} attributes. Supports EventArgs subclasses, lambda expressions, and async.
<button @onclick="Increment">Click me</button>
<input @onkeypress="OnKeyPress" />
@code {
private void Increment() => count++;
private void OnKeyPress(KeyboardEventArgs e) { ... }
}Prevent default and stop propagation with preventDefault and stopPropagation directives.
2.5 Data Binding
The @bind directive creates two-way binding between a component property and a C# variable/field.
<input @bind="name" />
<input @bind="age" @bind:event="oninput" /> <!-- immediate update -->Works with @bind:format for formatting, @bind:culture for locale. For custom components, implement @bind-Value with ValueChanged.
2.6 Render Fragments & Child Content
Accept arbitrary UI as a parameter using RenderFragment. The built-in ChildContent parameter captures the inner content.
<MyPanel>
<p>This is inside the panel.</p>
</MyPanel>
@* MyPanel.razor *@
<div class="panel">@ChildContent</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}Generic typed templates (RenderFragment<T>) enable contextual reuse.
2.7 Lifecycle Methods
| Method | Description |
|---|---|
SetParametersAsync | Runs first; sets parameters from parent. |
OnInitialized / OnInitializedAsync | Component initialised once. Good for loading data. |
OnParametersSet / Async | After parameters are set (each time). |
OnAfterRender / Async | After render. Interop with JS goes here. firstRender parameter. |
ShouldRender | Return false to skip re-render. |
StateHasChanged | Notify component to re-render. |
Dispose / DisposeAsync | Clean up resources. |
protected override async Task OnInitializedAsync()
{
data = await DataService.LoadAsync();
}2.8 @ref and @key
@ref captures a reference to a component or HTML element in C# code.
<MyComponent @ref="myComp" />
@code {
private MyComponent myComp;
// Now can call methods or access properties
}@key helps Blazor diff efficiently by giving elements a unique identity, preserving component instances or forcing recreation.
@foreach (var item in items)
{
<div @key="item.Id">@item.Name</div>
}3. Routing & Navigation
Blazor uses a client-side router. Pages are marked with the @page directive.
@page "/counter"
@page "/counter/{InitialCount:int}"
<h1>Counter</h1>- Route parameters:
{parameterName}with optional type constraints (:int,:guid,:alpha, etc.) - Catch-all:
{*pageRoute}for wildcard paths. - NavigationManager for programmatic navigation:
Navigation.NavigateTo("/counter"); Navigation.NavigateTo("/product/5", forceLoad: true); - Query strings: Use
[SupplyParameterFromQuery]in .NET 8, or parse viaNavigationManager.Uri. - NavLink component automatically applies an
activeclass.
4. Forms & Validation
Blazor provides the EditForm component with built-in validation support via data annotations.
<EditForm Model="model" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="model.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private MyModel model = new();
private void HandleValidSubmit() { ... }
}- Input components:
InputText,InputNumber,InputDate,InputSelect,InputCheckbox, etc. - Custom validation: implement
ValidationAttributeor useFluentValidationadapter. - .NET 8 introduced enhanced form handling:
enhancedattribute on form for SPAs,[SupplyParameterFromForm]for static SSR.
5. Dependency Injection
Services are registered in Program.cs and injected into components via @inject or the [Inject] attribute in code-behind.
@inject IDataService DataService
@code {
protected override async Task OnInitializedAsync()
{
items = await DataService.GetItemsAsync();
}
}OwningComponentBase.6. JavaScript Interop
Use IJSRuntime to call JavaScript functions from .NET. The preferred approach is using isolated JS modules (IJSObjectReference).
// Inject IJSRuntime
@inject IJSRuntime JS
// Call JS
await JS.InvokeVoidAsync("myFunction", arg);
// Module isolation
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/mymodule.js");
await module.InvokeVoidAsync("doSomething");Call .NET from JS with [JSInvokable] and DotNet.invokeMethodAsync.
7. State Management
- Component-internal state: fields/properties in
@code. - Service container (DI): Register a state service as scoped (server) or singleton (WASM) to share across components.
- Browser storage:
ProtectedLocalStorage/ProtectedSessionStoragefor persistency. - Flux pattern: Libraries like Fluxor, or
ComponentStatein server-side circuits. - .NET 8:
PersistentComponentStateenables preserving state during static SSR to interactive transition.
8. Error Handling
<ErrorBoundary>
<ChildComponent />
<ErrorBoundary>The ErrorBoundary component catches exceptions in its child content and renders an error UI. Use OnErrorAsync event to log. For global unhandled exceptions, customize the App.razor error layout.
9. Optimization & Performance
- Virtualization:
<Virtualize Items="bigList">for large lists. - Lazy loading: Defer loading assemblies for certain pages to reduce initial download size. Configure in
.csproj. - @key: Preserve component instances, avoid unnecessary re-renders.
- ShouldRender: Override to conditionally skip rendering.
- Render modes: Choose static SSR for content pages and InteractiveServer/WASM only where interactivity is needed.
- Streaming rendering: Use
@attribute [StreamRendering]to progressively deliver content.
10. Authentication & Authorization
<AuthorizeView>
<Authorized>Welcome, @context.User.Identity.Name</Authorized>
<NotAuthorized>You are not logged in.</NotAuthorized>
</AuthorizeView>Secure entire pages with the [Authorize] attribute in @attribute or code-behind. Use AuthenticationStateProvider to get the current user. Blazor supports JWT, cookies, and external providers through standard ASP.NET Core auth.
11. Advanced Component Patterns
Templated Components
@typeparam TItem
<ul>
@foreach (var item in Items)
{
<li>@ItemTemplate(item)</li>
}
</ul>
@code {
[Parameter] public IReadOnlyList<TItem> Items { get; set; }
[Parameter] public RenderFragment<TItem> ItemTemplate { get; set; }
}Generic Components
Use @typeparam to create reusable typed UI.
Dynamic Components
Render a component at runtime using DynamicComponent – parameterize its type and parameters.
CSS Isolation
Component.razor.css files automatically scope styles to that component, avoiding naming conflicts.
Code-behind
Use a partial class MyComponent.razor.cs to separate logic from markup for cleaner organisation.
12. Testing with bUnit
bUnit is the de-facto testing library for Blazor components. It renders components in a simulated environment, allowing you to assert markup, trigger events, and test interactions.
using var ctx = new Bunit.TestContext();
var cut = ctx.RenderComponent<MyCounter>(parameters =>
parameters.Add(p => p.InitialCount, 5));
cut.Find("button").Click();
cut.MarkupMatches("<h1>6</h1>");13. Resources & Next Steps
- Official Blazor Documentation
- Blazor samples on GitHub
- Component libraries: Fluent UI Blazor, Radzen, MudBlazor, Telerik UI for Blazor
- Stay up-to-date with .NET release notes for Blazor enhancements.
Happy Blazor coding! 🚀