Agent skill
dotnet-blazor
Build and review Blazor applications across server, WebAssembly, web app, and hybrid scenarios with correct component design, state flow, rendering, and hosting choices.
Install this agent skill to your Project
npx add-skill https://github.com/managedcode/dotnet-skills/tree/main/catalog/Frameworks/Blazor/skills/dotnet-blazor
SKILL.md
Blazor
Trigger On
- building interactive web UIs with C# instead of JavaScript
- choosing between Server, WebAssembly, or Auto render modes
- designing component hierarchies and state management
- handling prerendering and hydration
- integrating with JavaScript when necessary
Documentation
References
- patterns.md - Detailed component patterns, state management strategies, and JS interop techniques
- anti-patterns.md - Common Blazor mistakes and how to avoid them
Render Modes (.NET 8+)
| Mode | Where It Runs | Best For |
|---|---|---|
Static |
Server (no interactivity) | SEO pages, marketing content |
InteractiveServer |
Server via SignalR | Real-time apps, thin clients |
InteractiveWebAssembly |
Browser via WASM | Offline-capable, client-heavy |
InteractiveAuto |
Server first, then WASM | Best of both worlds |
Applying Render Modes
@* Per-component *@
@rendermode InteractiveServer
@* Or in App.razor for global *@
<Routes @rendermode="InteractiveAuto" />
InteractiveAuto Architecture
First Request:
Browser → Server (Interactive Server) → Fast response
Subsequent Requests:
Browser → WASM (downloaded in background) → No server needed
Workflow
-
Choose render mode based on requirements:
- Need SEO? Start with Static or prerendering
- Need real-time? Use InteractiveServer
- Need offline? Use InteractiveWebAssembly
- Want both? Use InteractiveAuto
-
Design components for reusability:
- Small, focused components
- Parameters for customization
- Events for communication
-
Handle state correctly:
- Component state lives in component
- Shared state via services (DI)
- Persist state across prerender with
[PersistentState]
-
Validate in both environments (for Auto mode)
Component Patterns
Basic Component
@* Counter.razor *@
<button @onclick="IncrementCount">
Clicked @count times
</button>
@code {
private int count = 0;
[Parameter]
public int InitialCount { get; set; } = 0;
protected override void OnInitialized()
{
count = InitialCount;
}
private void IncrementCount() => count++;
}
Parameter and Event Callbacks
@* Parent.razor *@
<ChildComponent Value="@value" ValueChanged="@OnValueChanged" />
@* ChildComponent.razor *@
@code {
[Parameter] public string Value { get; set; } = "";
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private async Task UpdateValue(string newValue)
{
await ValueChanged.InvokeAsync(newValue);
}
}
State Persistence (.NET 8+)
@* Prevents double-fetch during prerender + hydration *@
@code {
[PersistentState]
public List<Product> Products { get; set; } = [];
protected override async Task OnInitializedAsync()
{
// Only fetches once, persisted across prerender
Products ??= await Http.GetFromJsonAsync<List<Product>>("api/products");
}
}
Data Access Pattern for Auto Mode
// Shared interface
public interface IProductService
{
Task<List<Product>> GetProductsAsync();
}
// Server implementation (direct DB access)
public class ServerProductService : IProductService
{
private readonly AppDbContext _db;
public async Task<List<Product>> GetProductsAsync()
=> await _db.Products.ToListAsync();
}
// Client implementation (HTTP call)
public class ClientProductService : IProductService
{
private readonly HttpClient _http;
public async Task<List<Product>> GetProductsAsync()
=> await _http.GetFromJsonAsync<List<Product>>("api/products");
}
// Registration
// Server: builder.Services.AddScoped<IProductService, ServerProductService>();
// Client: builder.Services.AddScoped<IProductService, ClientProductService>();
Anti-Patterns to Avoid
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Large components | Hard to maintain, slow renders | Split into smaller components |
| Direct DB access in WASM | No DB in browser | Use HTTP API |
Ignoring ShouldRender |
Unnecessary re-renders | Override when needed |
| Sync JS interop in Server | Blocks SignalR circuit | Use IJSRuntime async |
| No error boundaries | One error crashes app | Use <ErrorBoundary> |
| Forgetting prerender state | Double API calls | Use [PersistentState] |
Performance Best Practices
-
Virtualize large lists:
razor<Virtualize Items="@products" Context="product"> <ProductCard Product="@product" /> </Virtualize> -
Use
@keyfor list diffing:razor@foreach (var item in items) { <ItemComponent @key="item.Id" Item="@item" /> } -
Debounce rapid events:
csharpprivate Timer? _debounceTimer; private void OnInput(ChangeEventArgs e) { _debounceTimer?.Dispose(); _debounceTimer = new Timer(_ => InvokeAsync(DoSearch), null, 300, Timeout.Infinite); } -
Lazy load assemblies (WASM):
csharpvar assemblies = await LazyAssemblyLoader .LoadAssembliesAsync(["MyHeavyFeature.wasm"]);
JS Interop
Calling JavaScript from C#
@inject IJSRuntime JS
await JS.InvokeVoidAsync("alert", "Hello from Blazor!");
var result = await JS.InvokeAsync<string>("prompt", "Enter name:");
Calling C# from JavaScript
[JSInvokable]
public static string GetMessage() => "Hello from C#!";
DotNet.invokeMethodAsync('MyAssembly', 'GetMessage')
.then(result => console.log(result));
Deliver
- interactive Blazor components with appropriate render mode
- efficient state management and data flow
- proper handling of prerendering scenarios
- performant list rendering with virtualization
Validate
- components render correctly in chosen mode
- state persists correctly across prerender/hydration
- no unnecessary re-renders (check with browser tools)
- JS interop works in both Server and WASM
- error boundaries catch component failures
- Auto mode works in both environments
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dotnet-project-setup
Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development.
csharp-scripts
Run single-file C# programs as scripts (file-based apps) for quick experimentation, prototyping, and concept testing. Use when the user wants to write and execute a small C# program without creating a full project.
dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging existing native interop code, wrapping a C or C++ library for use in .NET, diagnosing crashes, memory leaks, or corruption at the managed/native boundary. DO NOT USE FOR: COM interop, C++/CLI mixed-mode assemblies, or pure managed code with no native dependencies.
nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to private feeds or Azure Artifacts (OIDC is nuget.org only). INVOKES: shell (powershell or bash), edit, create, ask_user for guided repo setup.
dotnet-legacy-aspnet
Maintain classic ASP.NET applications on .NET Framework, including Web Forms, older MVC, and legacy hosting patterns, while planning realistic modernization boundaries.
dotnet-code-review
Review .NET changes for bugs, regressions, architectural drift, missing tests, incorrect async or disposal behavior, and platform-specific pitfalls before you approve or merge them.
Didn't find tool you were looking for?