Blazor component model including render modes, lifecycle, parameters, event handling, and component communication
From dotnet-blazornpx claudepluginhub markus41/claude --plugin dotnet-blazorThis skill is limited to using the following tools:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Analyzes BMad project state from catalog CSV, configs, artifacts, and query to recommend next skills or answer questions. Useful for help requests, 'what next', or starting BMad.
The unified Blazor Web App model supports four render modes:
@rendermode directive@page "/about"
@attribute [StreamRendering] @* Enable streaming for async data *@
<h1>About Us</h1>
<p>@_content</p>
@code {
private string? _content;
protected override async Task OnInitializedAsync()
{
_content = await ContentService.GetAboutAsync();
}
}
@rendermode InteractiveServer@rendermode InteractiveWebAssembly@rendermode InteractiveAutoConstructor → SetParametersAsync → OnInitialized[Async] → OnParametersSet[Async]
→ BuildRenderTree → OnAfterRender[Async](firstRender: true)
Parameter change → SetParametersAsync → OnParametersSet[Async]
→ BuildRenderTree → OnAfterRender[Async](firstRender: false)
Disposal → Dispose/DisposeAsync
OnInitializedAsync runs once - use for initial data loadingOnParametersSetAsync runs on every parameter change - use for dependent dataOnAfterRenderAsync runs after DOM update - use for JS interopfirstRender in OnAfterRenderAsync to avoid duplicate JS calls@* Parent *@
<ChildComponent Title="Hello" Items="@_items" OnDelete="HandleDelete" />
@* Child *@
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public List<Item> Items { get; set; } = [];
[Parameter] public EventCallback<int> OnDelete { get; set; }
}
@* Child *@
<button @onclick="() => OnDelete.InvokeAsync(item.Id)">Delete</button>
@* Layout or parent *@
<CascadingValue Value="@_theme" Name="AppTheme">
@Body
</CascadingValue>
@* Any descendant *@
@code {
[CascadingParameter(Name = "AppTheme")] public Theme? Theme { get; set; }
}
public sealed class AppState
{
public event Action? OnChange;
private int _count;
public int Count => _count;
public void Increment()
{
_count++;
OnChange?.Invoke();
}
}
// Register as Scoped (Server) or Singleton (WASM)
<EditForm Model="@_model" OnValidSubmit="HandleSubmit" FormName="my-form">
<DataAnnotationsValidator />
<InputText @bind-Value="_model.Name" />
<ValidationMessage For="@(() => _model.Name)" />
<InputNumber @bind-Value="_model.Quantity" />
<InputDate @bind-Value="_model.DueDate" />
<InputSelect @bind-Value="_model.Category">
@foreach (var cat in _categories)
{
<option value="@cat">@cat</option>
}
</InputSelect>
<button type="submit">Save</button>
</EditForm>
@page "/doctor-who-episodes/{season:int}"
@rendermode InteractiveWebAssembly
@using System.Globalization
@using BlazorSample.Components.Layout
@attribute [Authorize]
@implements IAsyncDisposable
@inject IJSRuntime JS
@inject ILogger<DoctorWhoEpisodes> Logger
<PageTitle>Doctor Who Episode List</PageTitle>
@* Disable prerender for specific component *@
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@* Disable for entire app in App.razor *@
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
@* Handle client-only services during prerender *@
@code {
protected override void OnInitialized()
{
// Check if service exists (null during server prerender)
if (Services.GetService<IWebAssemblyHostEnvironment>() is { } env)
environmentName = env.Environment;
}
}
// State container service
public class StateContainer
{
private string? savedString;
public string Property
{
get => savedString ?? string.Empty;
set { savedString = value; NotifyStateChanged(); }
}
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
}
// Register: AddScoped (Server), AddSingleton (WASM)
@implements IDisposable
@inject StateContainer StateContainer
<p>@StateContainer.Property</p>
@code {
protected override void OnInitialized() =>
StateContainer.OnChange += StateHasChanged;
public void Dispose() =>
StateContainer.OnChange -= StateHasChanged;
}
ProductDetail.razor (not productDetail.razor)Components/Pages/ProductDetail.razor@page "/product-detail"@namespace directive > RootNamespace in csproj > project namespace + folder path@* CounterPartialClass.razor *@
@page "/counter-partial-class"
<button @onclick="IncrementCount">Click me</button>
<p>Count: @currentCount</p>
// CounterPartialClass.razor.cs
namespace BlazorSample.Components.Pages;
public partial class CounterPartialClass
{
private int currentCount = 0;
private void IncrementCount() => currentCount++;
}
@key on repeated elements for efficient diffing<Virtualize> for large lists (renders only visible items)[SupplyParameterFromQuery] for query string binding[SupplyParameterFromForm] for form model binding in SSRNavigationManager.NavigateTo() for programmatic navigationIDisposable to unsubscribe from eventsErrorBoundary to catch component-level errors gracefully