Web UIs with Blazor
Blazor lets you build interactive web front-ends in C# instead of JavaScript. By the end of this lesson you'll understand Blazor components in .razor files, Razor syntax, passing data with [Parameter] , two-way binding with @bind , event handlers like @onclick , the OnInitializedAsync lifecycle, dependency injection with @inject , routing with @page , the difference between Blazor Server and Blazor WebAssembly , and forms with EditForm validation.
Learn Web UIs with Blazor in our free C# course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
Think of a web page as a wall built from LEGO bricks . Each Blazor component is a brick — a self-contained piece with its own shape (markup) and studs (parameters) that snap into a parent. A [Parameter] is a stud the parent presses data onto; @bind is a two-way connector so a brick and its data move together; an event like @onclick is a little switch on the brick. You assemble a whole page by clicking small, reusable bricks together — and because they're all the same C# you already know, you don't switch languages to do it.
The mental model: components that re-render on state change
A Blazor app is a tree of components . Each component is a .razor file mixing HTML-like markup with C# in a @code block. The component holds state (fields), and when that state changes — typically from an event handler — Blazor automatically re-renders the component and updates only the parts of the page that changed. You describe what the UI should look like for a given state; Blazor handles the DOM.
- 🧱 Component — a reusable .razor unit of UI plus its logic.
- 🎚️ [Parameter] — data a parent passes into a child component.
- 🔁 @bind — two-way binding between an input and a C# value.
- ⚡ @onclick — an event handler that runs a C# method, after which the component re-renders.
This is the same component-and-state idea behind every modern front-end framework — here it's just C# all the way down.
📊 Razor Directives & Syntax
Syntax
Means
Example
@page
Route for the component
@page "/counter"
@code
C# logic & state block
@code { ... }
[Parameter]
Input from a parent
[Parameter] public string Name
@bind
Two-way binding
<input @bind="name" />
@onclick
Click event handler
@onclick="Save"
@inject
Dependency injection
@inject HttpClient Http
In Razor, an @ switches from markup into C#: @name prints a value, and @if / @foreach drive the markup.
1. Components, State, and Events
A Blazor component is a .razor file: HTML-like markup at the top, a @code block of C# below. Fields in @code are the component's state , and you display them in markup with @field . Wire a button to a method with @onclick ; when the method changes state, Blazor automatically re-renders — no manual DOM updates. Read the classic counter and note how clicking updates the displayed count.
2. Two-Way Binding with @bind
@bind connects an input to a C# field in both directions: the input shows the current value, and the user's edits flow straight back into the field. Add @bind:event="oninput" to update on every keystroke rather than when the box loses focus. Because the field is the single source of truth, anything else that displays it updates live too.
Your turn. This toggle just needs its click wired up and the boolean flipped. Fill in the two ___ blanks using the hints.
3. Reusable Components with [Parameter]
Components become reusable when a parent can pass data into them. Mark a public property with [Parameter] and the parent sets it like an HTML attribute: <GreetingCard Name="Ada" /> . This is how you build a small component once and render it many times with different data — cards, list rows, badges, and so on.
Now you try. Finish this reusable price tag by exposing both fields as parameters so a parent can set them. Fill in the two ___ blanks:
4. Dependency Injection and Lifecycle
Components often need services — an HttpClient to call an API, a custom data service. @inject asks the DI container for one and assigns it to a property. To load data, override OnInitializedAsync , which runs once when the component initializes and lets you await the work; when it finishes, Blazor re-renders with the results. Show a "Loading..." placeholder until the data arrives.
🔎 Deep Dive: Blazor Server vs Blazor WebAssembly
Blazor runs your components in one of two hosting models, and the component code looks almost identical in both:
- Blazor Server — your C# runs on the server . UI events travel to the server over a persistent SignalR connection, and the server sends back a small diff of what changed. Tiny initial download and full server resources, but it needs a live connection and adds network latency per interaction.
- Blazor WebAssembly — a .NET runtime is downloaded and your C# runs in the browser via WebAssembly. After the initial load it works offline and feels instant, at the cost of a larger first download.
Because the programming model is shared, you can often start with one model and switch later with minimal changes.
Putting It Together: a Validated Sign-Up Form
Here's a realistic form that ties the lesson together. An EditForm binds to a model; a DataAnnotationsValidator turns [Required] and [EmailAddress] attributes into live validation; InputText uses @bind-Value ; and OnValidSubmit only fires once the input is valid. ValidationMessage shows the error next to each field.
Validation, binding, and submission are all C# — the same skills from the rest of the course, now driving a web form.
Pro Tips
- 💡 Load data in OnInitializedAsync , not in the constructor — it can await , and Blazor re-renders when it completes.
- 💡 Use @bind:event="oninput" when you want updates on every keystroke instead of on blur.
- 💡 Keep components small and parameter-driven so they're reusable and easy to reason about.
- 💡 Call StateHasChanged() only when state changes outside Blazor's awareness (e.g. a timer callback) — normal event handlers re-render for you.
- 💡 Pick the host by need: Blazor Server for tiny downloads and server access; WebAssembly for offline, low-latency, client-side apps.
Common Errors (and the fix)
- UI doesn't update after changing data: you changed state outside an event handler (e.g. a timer) — call StateHasChanged() so Blazor re-renders.
- "No service for type ... has been registered": you @inject ed a service that wasn't registered in DI. Register it in Program.cs (e.g. builder.Services.AddScoped<...>() ).
- Parameter never gets set: the property isn't public or is missing [Parameter] . Both are required for a parent to pass a value in.
- Click handler doesn't fire: you wrote onclick instead of @onclick , or referenced a method name that doesn't match the one in @code .
- Form submits with invalid data: you used OnSubmit instead of OnValidSubmit , or forgot the <DataAnnotationsValidator /> inside the EditForm .
📋 Quick Reference
Task
Code
Notes
Make a route
@page "/path"
Top of the .razor file
Show a value
@count
@ switches to C#
Handle a click
@onclick="DoThing"
Re-renders after
Two-way bind
@bind="name"
Input ↔ field
Accept input
[Parameter] public ...
From a parent
Inject a service
From DI
Load on init
OnInitializedAsync()
await data here
Validated form
<EditForm Model="m">
+ DataAnnotationsValidator
Frequently Asked Questions
For most UI logic, yes — you write components in C#. You can still call JavaScript when you need a browser API or a JS library (via JS interop), but day-to-day interactivity is C#.
Q: Should I choose Blazor Server or WebAssembly?
Server gives a tiny initial download and direct server access but needs a live SignalR connection. WebAssembly runs in the browser, works offline, and feels instant after a larger first load. The component code is nearly identical either way.
Q: What's the difference between @bind and @onclick ?
@bind creates a two-way data link between an input and a C# value. @onclick (and other @on... handlers) runs a C# method in response to an event, after which the component re-renders.
Q: Why use OnInitializedAsync instead of the constructor for loading data?
The constructor can't await , and parameters aren't set yet. OnInitializedAsync runs after the component is set up, can await async calls, and triggers a re-render when the data is ready.
Mini-Challenge: A Live Tip Calculator
No blanks this time — just a brief and an outline. Build a component with bound bill and tipPercent inputs and a computed Tip property that updates live as the user types. For a bonus, also show the grand total. Run it and watch the tip recalculate on every keystroke.
🎉 Lesson Complete
- ✅ Blazor builds interactive web UIs in C# using .razor components
- ✅ State lives in @code ; changing it triggers an automatic re-render
- ✅ [Parameter] passes data into components; @bind creates two-way bindings
- ✅ @onclick handles events; OnInitializedAsync loads data on init
- ✅ @inject brings in services; @page defines routes
- ✅ Blazor Server runs C# on the server over SignalR; WebAssembly runs it in the browser; EditForm validates forms
- ✅ Congratulations — you've reached the end of the C# course! You can now build console, desktop, game, and web apps in C#.
Practice quiz
What does Blazor let you build that previously required JavaScript?
- Interactive web user interfaces written in C#
- Mobile device drivers
- SQL database engines
- Operating system kernels
Answer: Interactive web user interfaces written in C#. Blazor lets you build interactive web UIs using C# and Razor components instead of writing the UI logic in JavaScript.
What file extension do Blazor components use?
- .xaml
- .cshtml
- .razor
- .blazor
Answer: .razor. Blazor components are written in .razor files, which combine Razor markup (HTML plus C#) with component logic.
Which attribute marks a C# property as a component parameter that a parent can set?
The [Parameter] attribute exposes a public property so a parent component can pass a value into the child, like an attribute in markup.
In Razor markup, how do you wire a button click to a C# method named Increment?
- click={Increment}
- @onclick="Increment"
- bind:click=Increment
- onclick=Increment
Answer: @onclick="Increment". Razor event handlers use the @on prefix; @onclick="Increment" calls the C# method when the button is clicked.
What does @bind do on an input element?
- Sends the value to a database
- Deletes the element
- Adds CSS classes only
- Creates a two-way binding between the input and a C# field/property
Answer: Creates a two-way binding between the input and a C# field/property. @bind sets up two-way data binding: the input shows the C# value and writes user edits back into that field or property.
Which lifecycle method is the right place to load data asynchronously when a component initializes?
- OnInitializedAsync()
- Render()
- Dispose()
- Main()
Answer: OnInitializedAsync(). OnInitializedAsync() runs when the component is first initialized and is the standard place to await data loading such as an API call.
How do you request a service via dependency injection inside a .razor component?
- @page the service
- @inject SomeService Name
- @using the service
- new Service()
Answer: @inject SomeService Name. The @inject directive asks the DI container for a service and assigns it to a property, e.g. @inject HttpClient Http.
What is the key difference between Blazor Server and Blazor WebAssembly?
- Server uses C# while WebAssembly uses Java
- WebAssembly cannot render HTML
- There is no difference
- Server runs C# on the server with UI updates over a SignalR connection; WebAssembly runs the .NET code in the browser
Answer: Server runs C# on the server with UI updates over a SignalR connection; WebAssembly runs the .NET code in the browser. Blazor Server executes component logic on the server and pushes UI diffs to the browser over SignalR; Blazor WebAssembly downloads a .NET runtime and runs the C# directly in the browser.
Which directive at the top of a component defines its route, e.g. /counter?
- @route
- @url
- @page "/counter"
- @map
Answer: @page "/counter". @page "/counter" sets the route template so navigating to /counter renders that routable component.
What does an EditForm with DataAnnotationsValidator provide?
- Automatic database migrations
- Model-based form validation that shows messages when input is invalid
- Image compression
- Server logging only
Answer: Model-based form validation that shows messages when input is invalid. EditForm binds to a model and, with DataAnnotationsValidator, validates it against data-annotation attributes, surfacing validation messages in the UI.
Continue this course
- Previous: Desktop Apps with WPF and XAML
- Next: Nullable Reference Types