Blazor Tutorial: Learn WebAssembly UI from Scratch (2026)
The first time I saw Blazor running .NET in the browser via WebAssembly, I felt the same thrill I felt when Silverlight was announced — except this time it was real. No plugin required, no proprietary runtime to install, just WebAssembly executing compiled C# at near-native speed. I built a dashboard application that needed complex charting and real-time data, and Blazor let me write the entire thing in C# — frontend and backend — sharing validation logic and data access code between both.
Blazor is a framework for building interactive web UIs with .NET. It offers two hosting models: Blazor Server where UI logic runs on the server and UI updates are streamed to the browser via SignalR, and Blazor WebAssembly where the entire app runs in the browser. The component model is consistent across both, so you can switch hosting models or even run both in the same app. This tutorial covers the skills you need to build real Blazor applications.
Blazor Hosting Models: Server vs WebAssembly
Blazor Server executes your component code on the server. The UI is rendered as HTML/DOM changes and sent to the browser over a persistent SignalR connection. This means your app loads almost instantly — only a small JavaScript file is downloaded — and you have full access to server-side resources like databases and authenticated services without exposing APIs. The trade-off is that every user interaction requires a round trip to the server, which can introduce latency over high-ping connections.
Blazor WebAssembly downloads the .NET runtime, your assemblies, and your app to the browser. The app runs entirely client-side, enabling offline scenarios and eliminating server dependency for UI rendering. Initial load time is longer because of the runtime download, but interaction is instant after that. .NET 8 introduced the ability to interactively render components based on user preference, combining the best of both worlds.
// Blazor Server - Program.cs
builder.Services.AddServerSideBlazor();
// Blazor WebAssembly - Program.cs
builder.Services.AddBlazorWebView();
// Interactive rendering in .NET 8
RenderMode = RenderMode.InteractiveServer;
// or
RenderMode = RenderMode.InteractiveWebAssembly;
Components, Parameters, and Event Handling
Blazor components are self-contained units of UI built with Razor. A component has a .razor file containing HTML markup and C# code, and optionally a code-behind .razor.cs file. Components accept data through parameters decorated with the [Parameter] attribute. Callback parameters use EventCallback
Event handling follows a familiar pattern: @onclick, @onchange, @onsubmit, and so on. These map to .NET event handlers, not JavaScript. You can prevent default behavior, stop propagation, and use async handlers naturally. Forms use the EditForm component with data annotations validation, providing server-style validation in the browser.
@* ProductCard.razor *@
@Product.Name
@Product.Price.ToString("C")
@code {
[Parameter] public Product Product { get; set; } = new();
[Parameter] public EventCallback OnSelected { get; set; }
}
Dependency Injection in Blazor
Blazor leverages the same dependency injection container as ASP.NET Core. Services registered in Program.cs are available to components and pages. Blazor Server uses scoped services per circuit (per user connection), while Blazor WebAssembly uses singleton services within the browser. Understanding this distinction is critical — scoped services in WebAssembly would essentially be singletons because there is only one user per browser instance.
Inject services into components using the @inject directive or the [Inject] attribute on properties. For services that need cleanup, implement IDisposable or IAsyncDisposable on the component. The OwningComponentBase class creates a service scope for the component, ensuring services are disposed when the component is destroyed — useful for transient DbContext instances.
@inject IProductService ProductService
@inject IJSRuntime JS
@code {
[Inject]
private IConfiguration Configuration { get; set; } = default!;
protected override async Task OnInitializedAsync()
{
Products = await ProductService.GetAllAsync();
}
private async Task DeleteProduct(int id)
{
await ProductService.DeleteAsync(id);
await JS.InvokeVoidAsync("alert", "Product deleted");
}
}
Routing and Navigation
Blazor routing uses the @page directive to define URL templates for components. The router component (found in App.razor) intercepts navigation and renders the matching component. Route parameters are defined with curly braces, and optional parameters use the ? suffix. The NavLink component renders navigation links with automatic active state highlighting.
Programmatic navigation uses the NavigationManager service injected into components. The NavigateTo method accepts a URL and optional flags for force load (avoiding the SPA) and replace (not adding to browser history). For advanced routing, you can define route constraints, catch-all parameters, and custom route handlers with the Microsoft.AspNetCore.Components.Routing namespace.
@page "/products/{Category?}"
@page "/products/{Category}/details/{Id:int}"
@inject NavigationManager Navigation
Products in @Category
@code {
[Parameter] public string? Category { get; set; }
[Parameter] public int Id { get; set; }
}
Forms and Validation
Blazor's EditForm component provides a structured approach to form handling with built-in validation. The model bound to the form uses data annotations for validation rules. The DataAnnotationsValidator directive enables server-side validation in the browser. ValidationSummary displays all errors, while ValidationMessage shows errors for individual fields.
Custom validation can be achieved through custom ValidationAttribute subclasses or by implementing IValidatableObject on the model. The InputText, InputNumber, InputDate, InputSelect, and InputCheckbox components bind to model properties and display validation styles automatically. For complex forms, multiple EditForm instances can be nested, and form state can be inspected to enable or disable submit buttons based on validity.
@code {
private Product product = new();
private async Task HandleValidSubmit()
{
await ProductService.AddAsync(product);
}
}
JavaScript Interop and Advanced Scenarios
Despite Blazor running C# in the browser, JavaScript interop is essential for accessing browser APIs that are not yet wrapped by .NET. The IJSRuntime service provides InvokeAsync
Advanced scenarios include rendering custom JavaScript libraries like Chart.js or Google Maps, handling browser storage (localStorage/sessionStorage), and integrating with third-party authentication providers. The ComponentBase lifecycle — SetParametersAsync, OnInitializedAsync, OnParametersSetAsync, OnAfterRenderAsync — gives you hooks to manage JavaScript interop timing, especially for scenarios that require DOM access after rendering is complete.
@inject IJSRuntime JS
@code {
private ElementReference chartContainer;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("renderChart", chartContainer, ChartData);
}
}
public async ValueTask DisposeAsync()
{
await JS.InvokeVoidAsync("destroyChart", chartContainer);
}
}
// JavaScript module:
// export function renderChart(container, data) { ... }
// export function destroyChart(container) { ... }
Frequently Asked Questions
Should I use Blazor Server or Blazor WebAssembly?
Blazor Server is best for intranet apps with low-latency connections, where instant load time matters. Blazor WebAssembly is better for public-facing apps, offline scenarios, or when you need to reduce server load for UI rendering.
Can I reuse my existing C# code in Blazor?
Yes. You can share validation logic, data models, services, and even entire class libraries between Blazor and other .NET projects. This is one of Blazor's biggest advantages over JavaScript frameworks.
How does Blazor compare to React or Angular?
Blazor targets .NET developers who want to build web UIs without JavaScript. React and Angular have larger ecosystems and more third-party libraries. Blazor's .NET integration and shared code between client and server are its key differentiators.
Can I use JavaScript libraries with Blazor?
Yes, through JavaScript interop (IJSRuntime). You can call any JavaScript function from C# and vice versa. Many popular libraries like Chart.js, Google Maps, and SignalR have Blazor-friendly wrappers.
Originally published on Ayodhyyya. Last updated June 1, 2026.