blazor

Applies to: Blazor (.NET 8+), C# 12+, WebAssembly, Server, Blazor United, Interactive Web Apps

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "blazor" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-blazor

Blazor Guide

Applies to: Blazor (.NET 8+), C# 12+, WebAssembly, Server, Blazor United, Interactive Web Apps

Core Principles

  • Component-Based: Build encapsulated .razor components that own their state and rendering

  • Hosting Flexibility: Choose WebAssembly, Server, or Blazor United (hybrid) per-component

  • C# Everywhere: Share models, validation, and logic between client and server

  • Type Safety: Leverage nullable reference types and strong parameter typing

  • Lifecycle Awareness: Understand component lifecycle to avoid leaks and race conditions

Hosting Models

Model Runs In Best For

WebAssembly Browser (WASM) Offline-capable apps, PWAs, reduced server load

Server Server (SignalR) Low-latency, SEO, thin clients

United (.NET 8) Hybrid SSR + interactive Per-component render mode selection

Guardrails

Component Rules

  • Keep components under 200 lines (split with child components)

  • Use [Parameter, EditorRequired] for required parameters

  • Never mutate [Parameter] properties directly -- use local state instead

  • Use EventCallback<T> for parent-child communication (not Action<T> )

  • Implement IDisposable when subscribing to events, timers, or state changes

  • Use @key directive on list-rendered items for DOM diffing performance

  • Wrap component trees in <ErrorBoundary> for graceful error handling

  • Use RenderFragment and RenderFragment<T> for templated components

Lifecycle Rules

  • Do NOT call JS interop in OnInitialized /OnInitializedAsync -- use OnAfterRender

  • Use OnParametersSet for reacting to parameter changes (not property setters)

  • Call StateHasChanged() only when necessary (avoid triggering excessive re-renders)

  • Use ShouldRender() override to prevent unnecessary renders on hot paths

  • Always use CancellationToken with async lifecycle methods to handle disposal

Data Binding

  • Use @bind-Value for two-way binding with input components

  • Use @bind:event to control when binding updates (e.g., oninput vs onchange )

  • Use EditForm with DataAnnotationsValidator for form validation

  • Never use @bind and @onchange on the same element (they conflict)

Security

  • Use [Authorize] attribute on pages requiring authentication

  • Use <AuthorizeView> for conditional UI based on auth state

  • Validate all inputs server-side (client validation is for UX, not security)

  • Never trust Blazor WASM for authorization decisions -- enforce on API

  • Use antiforgery tokens for form submissions in SSR mode

Performance

  • Use <Virtualize> for large lists instead of @foreach

  • Use @rendermode appropriately (static SSR by default, interactive only when needed)

  • Use [StreamRendering] for pages with async data loading

  • Minimize JS interop calls (batch operations when possible)

  • Use IMemoryCache or Blazored.LocalStorage for client-side caching

  • Lazy-load assemblies for large WASM apps with LazyAssemblyLoader

Project Structure

MyApp/ ├── MyApp.Client/ # Blazor WebAssembly / interactive │ ├── Pages/ # Routable components (@page) │ │ ├── Home.razor │ │ └── Users/ │ │ ├── UserList.razor │ │ ├── UserDetail.razor │ │ └── UserForm.razor │ ├── Components/ # Reusable components │ │ ├── Layout/ │ │ │ ├── MainLayout.razor │ │ │ └── NavMenu.razor │ │ ├── Common/ │ │ │ ├── LoadingSpinner.razor │ │ │ ├── ErrorBoundary.razor │ │ │ └── Modal.razor │ │ └── Users/ │ │ └── UserCard.razor │ ├── Services/ # Client-side services │ │ ├── IUserService.cs │ │ └── UserService.cs │ ├── State/ # State management │ │ └── AppState.cs │ ├── wwwroot/ # Static assets │ ├── _Imports.razor # Global using directives │ ├── App.razor # Root component │ └── Program.cs # DI and startup ├── MyApp.Server/ # API / server host │ ├── Controllers/ │ └── Program.cs ├── MyApp.Shared/ # Shared models and DTOs │ ├── Models/ │ └── DTOs/ ├── tests/ │ ├── MyApp.Client.Tests/ # bUnit component tests │ └── MyApp.Server.Tests/ # API integration tests └── MyApp.sln

Layer Responsibilities

Layer Purpose Depends On

Client UI components, pages, client services Shared

Server API endpoints, server logic, auth Shared

Shared Models, DTOs, validation attributes Nothing

Tests bUnit component tests, API tests Client, Server

Component Patterns

Page Component with Data Loading

@page "/users" @attribute [Authorize] @inject IUserService UserService

<PageTitle>Users</PageTitle>

@if (loading) { <LoadingSpinner /> } else if (error is not null) { <div class="alert alert-danger"> <p>@error</p> <button class="btn btn-outline-danger" @onclick="LoadUsers">Retry</button> </div> } else if (users is null || users.Count == 0) { <div class="alert alert-info">No users found.</div> } else { @foreach (var user in users) { <UserCard @key="user.Id" User="user" OnEdit="() => Edit(user.Id)" /> } }

@code { private List<UserResponse>? users; private bool loading = true; private string? error;

protected override async Task OnInitializedAsync()
{
    await LoadUsers();
}

private async Task LoadUsers()
{
    loading = true;
    error = null;
    try
    {
        users = await UserService.GetUsersAsync();
    }
    catch (Exception ex)
    {
        error = ex.Message;
    }
    finally
    {
        loading = false;
    }
}

private void Edit(long id) => Navigation.NavigateTo($"/users/{id}/edit");

}

Reusable Component with Parameters

<div class="card h-100"> <div class="card-body"> <h5 class="card-title">@User.FirstName @User.LastName</h5> <small class="text-muted">@User.Email</small> <span class="badge @(User.Active ? "bg-success" : "bg-secondary")"> @(User.Active ? "Active" : "Inactive") </span> </div> <div class="card-footer"> <button class="btn btn-sm btn-outline-primary" @onclick="OnEdit">Edit</button> <button class="btn btn-sm btn-outline-danger" @onclick="OnDelete">Delete</button> </div> </div>

@code { [Parameter, EditorRequired] public UserResponse User { get; set; } = default!;

[Parameter]
public EventCallback OnEdit { get; set; }

[Parameter]
public EventCallback OnDelete { get; set; }

}

Templated Component (RenderFragment)

@code { [Parameter] public string Title { get; set; } = "Modal"; [Parameter] public RenderFragment? BodyContent { get; set; } [Parameter] public RenderFragment? FooterContent { get; set; } [Parameter] public EventCallback OnClose { get; set; } }

Use RenderFragment for content slots, RenderFragment<T> for typed template parameters.

Data Binding & Forms

EditForm with Validation

<EditForm Model="model" OnValidSubmit="HandleSubmit"> <DataAnnotationsValidator /> <ValidationSummary class="alert alert-danger" />

&#x3C;div class="mb-3">
    &#x3C;label for="email" class="form-label">Email&#x3C;/label>
    &#x3C;InputText id="email" class="form-control" @bind-Value="model.Email" />
    &#x3C;ValidationMessage For="@(() => model.Email)" class="text-danger" />
&#x3C;/div>

&#x3C;button type="submit" class="btn btn-primary" disabled="@submitting">
    @(submitting ? "Saving..." : "Save")
&#x3C;/button>

</EditForm>

Routing

Route Parameters and Constraints

@page "/users/{Id:long}" @page "/users/{Id:long}/edit"

@code { [Parameter] public long Id { get; set; } }

Navigation

@inject NavigationManager Navigation

Navigation.NavigateTo("/users"); // Client-side navigation Navigation.NavigateTo("/users/1", forceLoad: true); // Full page reload

Use <NavLink> with Match="NavLinkMatch.Prefix" or NavLinkMatch.All for active CSS state.

State Management

Observable State Service

public class AppState { private UserResponse? _currentUser;

public UserResponse? CurrentUser
{
    get => _currentUser;
    set { _currentUser = value; NotifyStateChanged(); }
}

public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();

}

Consuming State in Components

@inject AppState State @implements IDisposable

<span>@State.CurrentUser?.FirstName</span>

@code { protected override void OnInitialized() { State.OnChange += StateHasChanged; }

public void Dispose()
{
    State.OnChange -= StateHasChanged;
}

}

CascadingValue for Shared Data

Use <CascadingValue Value="@theme"> in layouts. Consume with [CascadingParameter] in children. Good for theme, auth state, and locale. Avoid for frequently changing data (triggers full subtree re-render).

JavaScript Interop

Calling JS from C#

@inject IJSRuntime JS

@code { // Only call in OnAfterRenderAsync, not OnInitializedAsync protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var width = await JS.InvokeAsync<int>("blazorInterop.getWindowWidth"); } } }

Encapsulate JS interop behind service classes registered in DI. Use InvokeVoidAsync for calls without return values, InvokeAsync<T> for typed returns.

.NET 8 Blazor United

Interactive Render Modes

@* Static SSR (default -- no interactivity) *@ <StaticComponent />

@* Interactive Server (SignalR) *@ <InteractiveComponent @rendermode="InteractiveServer" />

@* Interactive WebAssembly *@ <InteractiveComponent @rendermode="InteractiveWebAssembly" />

@* Interactive Auto (starts Server, switches to WASM when downloaded) *@ <InteractiveComponent @rendermode="InteractiveAuto" />

Streaming Rendering

Add @attribute [StreamRendering] to pages with async data loading. The page renders immediately with placeholder content, then streams updates as OnInitializedAsync completes. Ideal for SSR pages loading slow data.

Testing Overview

Standards

  • Use bUnit for component unit tests

  • Use xUnit as the test runner

  • Use Moq or NSubstitute for service mocking

  • Use FluentAssertions for readable assertions

  • Test rendered markup, parameter behavior, and event callbacks

  • Coverage target: >80% for business logic components

Basic Component Test

using Bunit; using FluentAssertions;

public class UserCardTests : TestContext { [Fact] public void UserCard_DisplaysUserInfo() { var user = new UserResponse(1, "test@example.com", "John", "Doe", "User", true, DateTime.UtcNow);

    var cut = RenderComponent&#x3C;UserCard>(p => p.Add(x => x.User, user));

    cut.Find(".card-title").TextContent.Should().Contain("John Doe");
    cut.Find("small.text-muted").TextContent.Should().Contain("test@example.com");
}

[Fact]
public void UserCard_EditButton_InvokesCallback()
{
    var user = new UserResponse(1, "t@e.com", "J", "D", "User", true, DateTime.UtcNow);
    var clicked = false;

    var cut = RenderComponent&#x3C;UserCard>(p => p
        .Add(x => x.User, user)
        .Add(x => x.OnEdit, EventCallback.Factory.Create(this, () => clicked = true)));

    cut.Find("button.btn-outline-primary").Click();
    clicked.Should().BeTrue();
}

}

Tooling

Essential Commands

dotnet new blazorwasm -o MyApp.Client # New Blazor WASM app dotnet new blazorserver -o MyApp.Server # New Blazor Server app dotnet new blazor -o MyApp # New Blazor Web App (.NET 8 United) dotnet watch run --project MyApp.Client # Dev server with hot reload dotnet build # Build dotnet publish -c Release # Publish dotnet test # Run tests dotnet add package bunit # Add bUnit testing

Key Dependencies

Package Purpose

Microsoft.AspNetCore.Components.WebAssembly

WASM hosting

Microsoft.AspNetCore.Components.WebAssembly.Authentication

WASM auth

Blazored.LocalStorage

Browser local storage

Blazored.Toast

Toast notifications

bunit

Component testing

Fluxor.Blazor.Web

Redux-style state management

Best Practices Summary

DO

  • Use @key on list items for efficient DOM diffing

  • Use EventCallback over Action for component events

  • Implement IDisposable for event handler and timer cleanup

  • Use <Virtualize> for rendering large collections

  • Use typed HttpClient for API calls

  • Use CascadingValue for widely shared state (theme, auth)

  • Use @rendermode per-component in .NET 8 United

DO NOT

  • Call JS interop in OnInitialized (use OnAfterRender )

  • Mutate [Parameter] properties directly

  • Use synchronous HTTP calls

  • Ignore component lifecycle (leaks events and timers)

  • Overuse JS interop (prefer C# solutions)

  • Skip loading/error state handling in data-fetching components

Advanced Topics

For detailed patterns and examples, see:

  • references/patterns.md -- Forms, SignalR, authentication, testing, performance, error handling patterns

External References

  • Blazor Documentation

  • Blazor University

  • bUnit Testing

  • Blazored Libraries

  • Awesome Blazor

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

go-guide

No summary provided by upstream source.

Repository SourceNeeds Review