All skills
Skillintermediate
Blazor Patterns
```razor @* ProductList.razor *@ @page "/products" @inject IProductService ProductService @inject NavigationManager Navigation
Claude Code Knowledge Pack7/10/2026
Overview
Blazor Patterns
Component Basics
@* ProductList.razor *@
@page "/products"
@inject IProductService ProductService
@inject NavigationManager Navigation
Products
<h1>Products</h1>
@if (products is null)
{
<p><em>Loading...</em></p>
}
else if (!products.Any())
{
<p>No products found.</p>
}
else
{
<div class="product-grid">
@foreach (var product in products)
{
ViewDetails(product.Id))" />
}
</div>
}
@code {
private List? products;
protected override async Task OnInitializedAsync()
{
products = await ProductService.GetAllAsync();
}
private void ViewDetails(int id)
{
Navigation.NavigateTo($"/products/{id}");
}
}
Component Parameters
@* ProductCard.razor *@
<div class="card" @onclick="HandleClick">
<img src="@Product.ImageUrl" alt="@Product.Name" />
<h3>@Product.Name</h3>
<p class="price">@Product.Price.ToString("C")</p>
@if (ShowDescription)
{
<p>@Product.Description</p>
}
@ChildContent
</div>
@code {
[Parameter, EditorRequired]
public ProductDto Product { get; set; } = null!;
[Parameter]
public bool ShowDescription { get; set; }
[Parameter]
public EventCallback<int> OnClick { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
private async Task HandleClick()
{
await OnClick.InvokeAsync(Product.Id);
}
}
Form Handling and Validation
@* ProductForm.razor *@
@using System.ComponentModel.DataAnnotations
<div class="form-group">
<label>Name:</label>
model.Name)" />
</div>
<div class="form-group">
<label>Price:</label>
model.Price)" />
</div>
<div class="form-group">
<label>Category:</label>
<option value="">Select category...</option>
@foreach (var category in categories)
{
<option value="@category.Id">@category.Name</option>
}
model.CategoryId)" />
</div>
<button type="submit" class="btn btn-primary" disabled="@isSaving">
@(isSaving ? "Saving..." : "Save")
</button>
@code {
[Parameter]
public int? ProductId { get; set; }
[Parameter]
public EventCallback OnSaved { get; set; }
private ProductFormModel model = new();
private List categories = [];
private bool isSaving;
protected override async Task OnInitializedAsync()
{
categories = await CategoryService.GetAllAsync();
if (ProductId.HasValue)
{
var product = await ProductService.GetByIdAsync(ProductId.Value);
if (product is not null)
{
model = new ProductFormModel
{
Name = product.Name,
Price = product.Price,
CategoryId = product.CategoryId
};
}
}
}
private async Task HandleValidSubmit()
{
isSaving = true;
try
{
var product = ProductId.HasValue
? await ProductService.UpdateAsync(ProductId.Value, model)
: await ProductService.CreateAsync(model);
await OnSaved.InvokeAsync(product);
}
finally
{
isSaving = false;
}
}
private class ProductFormModel
{
[Required, StringLength(200)]
public string Name { get; set; } = string.Empty;
[Required, Range(0.01, 999999.99)]
public decimal Price { get; set; }
[Required]
public int CategoryId { get; set; }
}
}
State Management with Cascading Values
@* App.razor *@
@code {
private AppState appState = new();
}
// AppState.cs
public class AppState
{
public event Action? OnChange;
private int _cartItemCount;
public int CartItemCount
{
get => _cartItemCount;
set
{
if (_cartItemCount != value)
{
_cartItemCount = value;
NotifyStateChanged();
}
}
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
// Using cascading value
@code {
[CascadingParameter]
public AppState AppState { get; set; } = null!;
protected override void OnInitialized()
{
AppState.OnChange += StateHasChanged;
}
public void Dispose()
{
AppState.OnChange -= StateHasChanged;
}
}
JavaScript Interop
@inject IJSRuntime JS
@implements IAsyncDisposable
<div @ref="mapElement" style="height: 400px;"></div>
@code {
private ElementReference mapElement;
private IJSObjectReference? module;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Import JS module
module = await JS.InvokeAsync(
"import", "./js/mapComponent.js");
// Initialize map
mapInstance = await module.InvokeAsync(
"initializeMap", mapElement);
}
}
public async Task SetLocationAsync(double lat, double lng)
{
if (mapInstance is not null)
{
await mapInstance.InvokeVoidAsync("setLocation", lat, lng);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
await mapInstance.DisposeAsync();
if (module is not null)
await module.DisposeAsync();
}
}
// wwwroot/js/mapComponent.js
const map = new Map(element);
return {
setLocation: (lat, lng) => {
map.setView([lat, lng], 13);
}
};
}
Component Lifecycle
@implements IDisposable
@code {
protected override void OnInitialized()
{
// Called when component is initialized
// Use for non-async initialization
}
protected override async Task OnInitializedAsync()
{
// Called when component is initialized
// Use for async initialization (API calls, etc.)
await LoadDataAsync();
}
protected override void OnParametersSet()
{
// Called when parameters are set
// Use to react to parameter changes
}
protected override async Task OnParametersSetAsync()
{
// Async version of OnParametersSet
await ValidateParametersAsync();
}
protected override bool ShouldRender()
{
// Return false to prevent re-rendering
return true;
}
protected override void OnAfterRender(bool firstRender)
{
// Called after component renders
// firstRender is true only on first render
if (firstRender)
{
// One-time setup
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Async version - use for JS interop
if (firstRender)
{
await InitializeJavaScriptAsync();
}
}
public void Dispose()
{
// Cleanup resources
timer?.Dispose();
}
}
Authentication
@* LoginDisplay.razor *@
<span>Hello, @context.User.Identity?.Name!</span>
<button @onclick="LogOut">Log out</button>
<a href="authentication/login">Log in</a>
@code {
[Inject]
private NavigationManager Navigation { get; set; } = null!;
private void LogOut()
{
Navigation.NavigateTo("authentication/logout");
}
}
@* Protecting a page *@
@page "/admin"
@attribute [Authorize(Roles = "Admin")]
<h1>Admin Panel</h1>
@* Conditional rendering based on auth *@
<button>Delete All</button>
Error Boundaries
<div class="alert alert-danger">
<h4>An error occurred</h4>
<p>@exception.Message</p>
<button @onclick="RecoverAsync">Retry</button>
</div>
@code {
private ErrorBoundary? errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
private async Task RecoverAsync()
{
errorBoundary?.Recover();
await LoadDataAsync();
}
}
Virtualization for Large Lists
@using Microsoft.AspNetCore.Components.Web.Virtualization
<div class="product-item">
<h3>@product.Name</h3>
<p>@product.Price.ToString("C")</p>
</div>
@* Or with ItemsProvider for lazy loading *@
<div class="loading-skeleton"></div>
@code {
private async ValueTask<ItemsProviderResult> LoadProducts(
ItemsProviderRequest request)
{
var products = await ProductService.GetPageAsync(
request.StartIndex,
request.Count);
var totalCount = await ProductService.GetCountAsync();
return new ItemsProviderResult(products, totalCount);
}
}
SignalR Integration
// Program.cs
builder.Services.AddScoped();
// NotificationService.cs
public class NotificationService : IAsyncDisposable
{
private HubConnection? _hubConnection;
public async Task InitializeAsync(string hubUrl)
{
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.On<string>("ReceiveNotification", notification =>
{
OnNotificationReceived?.Invoke(notification);
});
await _hubConnection.StartAsync();
}
public event Action<string>? OnNotificationReceived;
public async ValueTask DisposeAsync()
{
if (_hubConnection is not null)
await _hubConnection.DisposeAsync();
}
}
@inject NotificationService NotificationService
@implements IDisposable
@if (!string.IsNullOrEmpty(lastNotification))
{
<div class="notification">@lastNotification</div>
}
@code {
private string? lastNotification;
protected override async Task OnInitializedAsync()
{
NotificationService.OnNotificationReceived += HandleNotification;
await NotificationService.InitializeAsync("/notificationHub");
}
private void HandleNotification(string notification)
{
lastNotification = notification;
StateHasChanged();
}
public void Dispose()
{
NotificationService.OnNotificationReceived -= HandleNotification;
}
}
Quick Reference
| Feature | Use Case | Notes |
|---|---|---|
@page | Route definition | Can have multiple routes |
@inject | Dependency injection | Or use [Inject] property |
@bind | Two-way binding | @bind-Value for components |
[Parameter] | Component input | Use [EditorRequired] when needed |
EventCallback | Component events | Type-safe callbacks |
RenderFragment | Child content | For flexible layouts |
CascadingValue | Shared state | Automatic to descendants |
AuthorizeView | Conditional auth UI | Or @attribute [Authorize] |
ErrorBoundary | Error handling | Catch render exceptions |
Virtualize | Large lists | Performance optimization |