Blazor & Blazor Components

 

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.

Key advantage: Full-stack .NET – use the same language, tooling, and ecosystem on client and server. Components are self-contained units of UI and logic, encouraging reusability and testability.

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 BlazorWebView

1.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 ModeDescription
Static SSRServer-side rendering without interactivity (like MVC/Razor Pages). Components render on server, no SignalR/WebSocket.
InteractiveServerClassic Blazor Server – interactivity via SignalR circuit.
InteractiveWebAssemblyClassic Blazor WASM – runs in browser after download.
InteractiveAutoStarts 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 EventCallback named 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

MethodDescription
SetParametersAsyncRuns first; sets parameters from parent.
OnInitialized / OnInitializedAsyncComponent initialised once. Good for loading data.
OnParametersSet / AsyncAfter parameters are set (each time).
OnAfterRender / AsyncAfter render. Interop with JS goes here. firstRender parameter.
ShouldRenderReturn false to skip re-render.
StateHasChangedNotify component to re-render.
Dispose / DisposeAsyncClean 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 via NavigationManager.Uri.
  • NavLink component automatically applies an active class.

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: InputTextInputNumberInputDateInputSelectInputCheckbox, etc.
  • Custom validation: implement ValidationAttribute or use FluentValidation adapter.
  • .NET 8 introduced enhanced form handling: enhanced attribute 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();
    }
}
Lifetime considerations: In Blazor Server, scoped services live per circuit (user). In WASM, scoped equals singleton because there's no request scope. For per-component scoping use 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 / ProtectedSessionStorage for persistency.
  • Flux pattern: Libraries like Fluxor, or ComponentState in server-side circuits.
  • .NET 8: PersistentComponentState enables 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

Happy Blazor coding! 🚀

Post a Comment

Previous Post Next Post