C# Blazor Basics Cheat Sheet
Covers Blazor component syntax, data binding, render modes (Server/WebAssembly/Auto), parameters, and component lifecycle for .NET 8+.
A Basic Component
`.razor` files mix HTML-like markup with C# in an `@code` block.
@page "/counter"<h1>Counter</h1><p>Current count: @count</p><button class="btn btn-primary" @onclick="Increment">Click me</button>@code { private int count = 0; private void Increment() { count++; }}
Render Modes (.NET 8+)
Choose per-component (or app-wide) whether it runs on the server, in WASM, or auto-switches.
@page "/dashboard"@rendermode InteractiveServer@* alternatives: InteractiveWebAssembly, InteractiveAuto, or omit for static SSR *@<h3>Live Dashboard</h3>@code { // InteractiveAuto: starts on Server for fast first load, // then switches to WASM once the .NET runtime downloads.}
Data Binding & Component Parameters
`@bind` for two-way binding, `[Parameter]` for parent-to-child data flow.
<!-- ChildComponent.razor --><input @bind="Name" @bind:event="oninput" /><p>Hello, @Name!</p>@code { [Parameter] public string Name { get; set; } = ""; [Parameter] public EventCallback<string> NameChanged { get; set; }}<!-- Parent.razor --><ChildComponent @bind-Name="parentName" />@code { private string parentName = "World";}
Lifecycle Methods & Injecting Services
`OnInitializedAsync` runs once; `@inject` pulls services from DI.
@page "/products"@inject IProductService ProductService@implements IDisposable<ul> @foreach (var p in products) { <li>@p.Name — @p.Price.ToString("C")</li> }</ul>@code { private List<Product> products = new(); protected override async Task OnInitializedAsync() { products = await ProductService.GetAllAsync(); } public void Dispose() { // cleanup subscriptions/timers here }}
Common Razor Directives
Directives you'll see at the top of most components.
- @page "/route"- makes the component routable at that URL
- @rendermode- sets Server/WebAssembly/Auto interactivity for the component
- @inject- injects a DI service as a property
- @bind- two-way data binding between an element and a field
- @onclick / @oninput- event binding to C# methods
- @code- block containing the component's C# members
- [Parameter]- marks a public property as settable from a parent component
CascadingValue & CascadingParameter
Flow data down an arbitrary component subtree without threading [Parameter] props through every level.
<!-- Parent.razor --><CascadingValue Value="theme"> <Layout> <ChildComponent /> </Layout></CascadingValue>@code { private ThemeInfo theme = new() { Color = "dark" };}<!-- ChildComponent.razor, any depth below -->@code { [CascadingParameter] public ThemeInfo? Theme { get; set; } // Named cascading values disambiguate when multiple values of the same type flow down [CascadingParameter(Name = "PageTitle")] public string? Title { get; set; }}
JS Interop with Isolated Modules
Load a JS module per-component via IJSRuntime and dispose it correctly to avoid leaking references.
@inject IJSRuntime JS@implements IAsyncDisposable<input @ref="inputRef" /><button @onclick="FocusInput">Focus</button>@code { private ElementReference inputRef; private IJSObjectReference? module; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/Focus.razor.js"); } private async Task FocusInput() { await inputRef.FocusAsync(); if (module is not null) await module.InvokeVoidAsync("logFocus", inputRef); } public async ValueTask DisposeAsync() { if (module is not null) await module.DisposeAsync(); }}
EditForm, DataAnnotations & Custom Validators
EditForm wires a model's data annotations into per-field validation messages and submit callbacks.
<EditForm Model="@model" OnValidSubmit="HandleValidSubmit"> <DataAnnotationsValidator /> <ValidationSummary /> <InputText @bind-Value="model.Email" /> <ValidationMessage For="() => model.Email" /> <button type="submit">Save</button></EditForm>@code { private UserForm model = new(); private void HandleValidSubmit() { // model already passed all [Required]/[EmailAddress] checks } public class UserForm { [Required, EmailAddress] public string Email { get; set; } = ""; [Range(18, 120, ErrorMessage = "Must be an adult")] public int Age { get; set; } }}
Generic, Templated Components
`@typeparam` plus RenderFragment<T> lets one component render arbitrary markup per item, fully type-checked.
<!-- ListView.razor -->@typeparam TItem<ul> @foreach (var item in Items) { <li>@ItemTemplate(item)</li> }</ul>@code { [Parameter, EditorRequired] public IReadOnlyList<TItem> Items { get; set; } = default!; [Parameter, EditorRequired] public RenderFragment<TItem> ItemTemplate { get; set; } = default!;}<!-- Usage --><ListView Items="products" TItem="Product"> <ItemTemplate Context="p"> <strong>@p.Name</strong> — @p.Price.ToString("C") </ItemTemplate></ListView>
Advanced APIs Worth Knowing
Lesser-used but production-relevant Blazor building blocks beyond the basics.
- ErrorBoundary- wraps a subtree, catches unhandled exceptions from it, and renders fallback UI instead of crashing the whole app
- PersistentComponentState- persists prerendered data into the page so an interactive component doesn't re-fetch it on startup
- ShouldRender()- override to suppress unnecessary re-renders when component state hasn't meaningfully changed
- StateHasChanged()- manually queues a re-render, needed after state changes outside Blazor's normal event pipeline
- @key- hints the diffing algorithm to preserve element/component identity when a list is reordered
- InputFile- built-in component for streaming file uploads without loading the whole file into memory
- QuickGrid- official high-performance data grid component with virtualization and sorting support
Default new Blazor Web Apps to static server-side rendering and only opt individual components into `@rendermode InteractiveServer/WebAssembly` where you actually need interactivity — this keeps initial page loads fast and avoids shipping unnecessary WASM.