Blazor
Priority: P1 (OPERATIONAL)
Blazor component patterns for interactive web UIs.
Implementation Guidelines
- •Components: Keep components focused. Split large components into smaller ones.
- •Parameters: Use
[Parameter]for one-way,EventCallbackfor two-way binding. - •Cascading Values: Use for app-wide state (theme, user context).
- •State Management: Component state for local, Fluxor/custom service for global.
- •Forms:
EditFormwithDataAnnotationsValidatoror FluentValidation. - •JS Interop: Use
IJSRuntimesparingly. Prefer Blazor bindings. - •Render Modes: Choose based on requirements (Server, WASM, Auto in .NET 8+).
- •Streaming: Use
[StreamRendering]for long-loading components.
Anti-Patterns
- •No direct DOM manipulation: Use Blazor bindings and refs.
- •No
StateHasChanged()in lifecycle: Called automatically after lifecycle methods. - •No heavy computation in render: Move to
OnInitializedor background task. - •No sync JS interop in Server mode: Causes UI blocking.
Code
razor
@* UserList.razor *@
@page "/users"
@attribute [StreamRendering]
@inject IUserService UserService
<PageTitle>Users</PageTitle>
<h3>Users</h3>
@if (_users is null)
{
<p><em>Loading...</em></p>
}
else if (_users.Count == 0)
{
<p>No users found.</p>
}
else
{
<div class="user-grid">
@foreach (var user in _users)
{
<UserCard User="user" OnDelete="HandleDelete" />
}
</div>
}
@code {
private List<User>? _users;
protected override async Task OnInitializedAsync()
{
_users = await UserService.GetAllAsync();
}
private async Task HandleDelete(int userId)
{
await UserService.DeleteAsync(userId);
_users = await UserService.GetAllAsync();
}
}
razor
@* UserCard.razor - Reusable component *@
<div class="card">
<h5>@User.Name</h5>
<p>@User.Email</p>
<button @onclick="() => OnDelete.InvokeAsync(User.Id)">Delete</button>
</div>
@code {
[Parameter, EditorRequired]
public User User { get; set; } = default!;
[Parameter]
public EventCallback<int> OnDelete { get; set; }
}
razor
@* EditForm with validation *@
<EditForm Model="@_model" OnValidSubmit="HandleSubmit" FormName="CreateUser">
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" />
<div class="mb-3">
<label class="form-label">Name</label>
<InputText @bind-Value="_model.Name" class="form-control" />
<ValidationMessage For="@(() => _model.Name)" />
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<InputText @bind-Value="_model.Email" class="form-control" type="email" />
<ValidationMessage For="@(() => _model.Email)" />
</div>
<button type="submit" class="btn btn-primary" disabled="@_isSubmitting">
@if (_isSubmitting)
{
<span class="spinner-border spinner-border-sm"></span>
}
Submit
</button>
</EditForm>
@code {
private CreateUserModel _model = new();
private bool _isSubmitting;
private async Task HandleSubmit()
{
_isSubmitting = true;
await UserService.CreateAsync(_model);
_isSubmitting = false;
NavigationManager.NavigateTo("/users");
}
}
Reference & Examples
For lifecycle, state management, and JS interop: See references/REFERENCE.md.
Related Topics
aspnet-core | razor-pages | security