Skip to main content

Blazor WebAssembly Tutorial: Build Web Apps with C#

Learn Blazor WebAssembly step by step. Build interactive web apps with C# instead of JavaScript. Includes code examples, best practices, and tips for 2026.

Blazor WebAssembly Tutorial — Build Interactive Web Apps with C#

If you've ever wished you could build rich, interactive web apps without writing a single line of JavaScript, this Blazor WebAssembly tutorial is exactly where you need to start. Blazor WebAssembly (Blazor WASM) lets you run C# directly in the browser using WebAssembly, giving .NET developers a first-class path to full-stack web development. In 2026, with .NET 9 mature and .NET 10 on the horizon, Blazor has evolved from an experimental framework into a production-ready platform trusted by enterprises worldwide.

In this guide, you'll learn how Blazor WebAssembly works under the hood, build a working application from scratch, master component architecture, handle HTTP calls, manage state, and avoid the most common pitfalls that trip up developers. Whether you're a C# backend developer stepping into the frontend or a JavaScript developer curious about alternatives, this tutorial covers everything you need to ship real-world C# web apps.

What Is Blazor WebAssembly and Why Should You Care?

Blazor WebAssembly is a client-side web framework from Microsoft that runs .NET code directly in the browser via WebAssembly. Unlike Blazor Server (which keeps a persistent SignalR connection to the server), Blazor WASM downloads a small .NET runtime and your compiled app to the client. Once loaded, everything runs locally — no round trips for UI interactions, no server dependency for rendering.

Here's why that matters in 2026:

  • One language, full stack: Write your API, business logic, and UI all in C#. Share models, validation, and utilities between client and server without any code duplication.
  • Offline capable: Since Blazor WASM runs entirely in the browser, you can build progressive web apps (PWAs) that work without an internet connection.
  • No JavaScript fatigue: Skip the webpack configs, npm dependency nightmares, and framework churn. Your existing .NET tooling — Visual Studio, Rider, dotnet CLI — works out of the box.
  • Performance improvements: .NET 9 brought AOT (Ahead-of-Time) compilation improvements, trimming optimizations, and the Webcil packaging format, making Blazor WASM apps significantly faster and smaller than earlier versions.

Blazor WebAssembly vs Blazor Server — Which Should You Choose?

This is one of the most common questions developers ask, and the answer depends on your scenario:

  • Blazor WebAssembly: Best for public-facing apps, PWAs, offline scenarios, and apps where you want to minimize server load. Initial load is slightly longer, but subsequent interactions are lightning fast.
  • Blazor Server: Best for internal line-of-business apps on fast networks, apps that need thin clients, or when you can't expose business logic to the client.
  • Blazor United / Interactive rendering (new): Starting with .NET 8 and refined in .NET 9, you can mix both models in a single app — use server-side rendering for fast initial loads and switch to WebAssembly for interactive components.

For this tutorial, we're focusing on the standalone Blazor WebAssembly model, which gives you the most control and the best understanding of the fundamentals.

Getting Started — Create Your First Blazor WASM Project

Make sure you have the .NET 9 SDK (or later) installed. Open your terminal and run:

// Terminal commands (not C# — run in your shell)
dotnet new blazorwasm -o MyBlazorApp
cd MyBlazorApp
dotnet run

Open your browser to https://localhost:5001 and you'll see the default Blazor template running. That's a fully functional C# web app running in your browser with zero JavaScript.

Let's look at the project structure that matters:

  • Program.cs — the entry point where you configure services and the root component.
  • wwwroot/ — static assets (CSS, images, index.html).
  • Pages/ — routable Razor components (your "pages").
  • Components/ or Shared/ — reusable UI components.

Building a Blazor Component — The Core Building Block

Everything in Blazor is a Blazor component. A component is a self-contained chunk of UI defined in a .razor file. It combines HTML markup with C# logic. Let's build a practical example — a searchable product list:

@page "/products"

<h3>Product Catalog</h3>

<input type="text" 
       @bind-value="SearchTerm" 
       @bind-value:event="oninput"
       placeholder="Search products..." 
       class="form-control mb-3" />

@if (FilteredProducts.Any())
{
    <ul class="list-group">
        @foreach (var product in FilteredProducts)
        {
            <li class="list-group-item d-flex justify-content-between">
                <span>@product.Name</span>
                <span class="badge bg-primary">@product.Price.ToString("C")</span>
            </li>
        }
    </ul>
}
else
{
    <p class="text-muted">No products found.</p>
}

@code {
    private string SearchTerm { get; set; } = string.Empty;

    private List<Product> Products { get; set; } = new()
    {
        new("Visual Studio License", 499.99m),
        new("ReSharper Annual", 149.00m),
        new("Azure DevOps Hosting", 29.99m),
        new(".NET Conf Workshop", 0.00m),
        new("C# Mastery Course", 79.99m)
    };

    private IEnumerable<Product> FilteredProducts =>
        string.IsNullOrWhiteSpace(SearchTerm)
            ? Products
            : Products.Where(p => p.Name.Contains(SearchTerm, StringComparison.OrdinalIgnoreCase));

    private record Product(string Name, decimal Price);
}

Notice what's happening here: the @bind-value:event="oninput" directive gives you real-time filtering as the user types — no JavaScript event listeners, no DOM manipulation, no jQuery. Pure C#.

Calling REST APIs with HttpClient

Most real-world apps need to fetch data from an API. Blazor WebAssembly provides a pre-configured HttpClient that works through the browser's Fetch API. Here's how to load data from a backend:

@page "/weather"
@inject HttpClient Http

<h3>Weather Forecast</h3>

@if (_forecasts is null)
{
    <p>Loading forecasts...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp (°C)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var f in _forecasts)
            {
                <tr>
                    <td>@f.Date.ToShortDateString()</td>
                    <td>@f.TemperatureC</td>
                    <td>@f.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? _forecasts;

    protected override async Task OnInitializedAsync()
    {
        _forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("api/weather");
    }

    private record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);
}

The key method is GetFromJsonAsync<T> — it handles the HTTP call and JSON deserialization in a single line. For production apps, register typed HttpClient instances in Program.cs to keep your base URLs and headers organized:

// Program.cs
builder.Services.AddHttpClient("WeatherApi", client =>
{
    client.BaseAddress = new Uri("https://api.myapp.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

State Management in Blazor WebAssembly

State management is where many developers stumble. Blazor doesn't ship with a built-in state management library like Redux or Vuex, but you don't need one for most applications. The simplest and most effective pattern is a cascading service:

// AppState.cs
public class AppState
{
    private string _currentUser = string.Empty;

    public string CurrentUser
    {
        get => _currentUser;
        set
        {
            _currentUser = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

// Register in Program.cs — use Singleton for Blazor WASM
builder.Services.AddSingleton<AppState>();
// Using it in a component
@inject AppState State
@implements IDisposable

<p>Welcome, @State.CurrentUser!</p>

@code {
    protected override void OnInitialized()
    {
        State.OnChange += StateHasChanged;
    }

    public void Dispose()
    {
        State.OnChange -= StateHasChanged;
    }
}

This pattern scales surprisingly well. Components subscribe to state changes, and when any component updates the state, all subscribers re-render automatically. For complex apps, consider Fluxor, a Flux/Redux-style library built specifically for Blazor.

Performance Optimization — Making Blazor WASM Fast

The most common criticism of Blazor WebAssembly is initial load time. Here's exactly how to fix that in 2026:

1. Enable AOT Compilation

Ahead-of-Time compilation converts your .NET IL code to native WebAssembly, dramatically improving runtime performance (at the cost of a larger download). Add this to your project file:

<!-- In your .csproj file -->
<PropertyGroup>
    <RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>

2. Enable Trimming and Compression

<!-- In your .csproj file -->
<PropertyGroup>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
    <BlazorEnableCompression>true</BlazorEnableCompression>
</PropertyGroup>

3. Use Lazy Loading for Routes

Don't load your entire app upfront. Split it into lazy-loaded assemblies so users only download what they need:

// In your App.razor or Router setup
<Router AppAssembly="typeof(App).Assembly"
        AdditionalAssemblies="lazyLoadedAssemblies"
        OnNavigateAsync="OnNavigateAsync">
    <!-- ... -->
</Router>

@code {
    private List<Assembly> lazyLoadedAssemblies = new();

    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "admin")
        {
            var assemblies = await LazyAssemblyLoader
                .LoadAssembliesAsync(new[] { "AdminModule.wasm" });
            lazyLoadedAssemblies.AddRange(assemblies);
        }
    }
}

With these three optimizations, you can get a Blazor WASM app's initial load under 2 seconds on a typical broadband connection — comparable to most React or Angular apps.

Common Pitfalls and Best Practices

After working with Blazor WebAssembly in production, here are the mistakes I see most often:

Pitfall 1: Calling StateHasChanged Unnecessarily

Blazor automatically re-renders a component after event handlers complete. Calling StateHasChanged() manually inside a button click handler is redundant and can cause double renders. Only call it when state changes outside of Blazor's event system (e.g., from a timer or external event).

Pitfall 2: Not Disposing Event Subscriptions

If your component subscribes to events (like our AppState.OnChange example above), you must implement IDisposable and unsubscribe. Otherwise, you'll leak memory and cause ghost re-renders from components that are no longer visible.

Pitfall 3: Blocking the UI Thread

Blazor WASM runs on a single thread. If you run a heavy computation synchronously, the entire UI freezes. Always use async/await for I/O operations, and for CPU-bound work, consider offloading to a Web Worker or breaking the work into small chunks with Task.Yield():

private async Task ProcessLargeDataset(List<DataItem> items)
{
    for (int i = 0; i < items.Count; i++)
    {
        ProcessItem(items[i]);

        if (i % 100 == 0)
        {
            StatusMessage = $"Processing {i}/{items.Count}...";
            await Task.Yield(); // Let the UI update
        }
    }
}

Pitfall 4: Exposing Sensitive Logic to the Client

Remember, Blazor WASM runs in the browser. Your compiled DLLs are downloadable and decompilable. Never put secrets, connection strings, or sensitive business logic in your WASM project. Always validate and authorize on the server.

Best Practices Checklist

  • Keep components small and focused — one responsibility per component.
  • Use [Parameter] for parent-to-child communication and EventCallback for child-to-parent.
  • Register services as Singleton in Blazor WASM (there's no scoped lifetime — the app IS the scope).
  • Use @key on list items to help Blazor's diffing algorithm perform efficiently.
  • Virtualize large lists with the built-in <Virtualize> component instead of rendering thousands of DOM elements.

Blazor vs JavaScript Frameworks — A Practical Comparison

Developers constantly ask: should I choose Blazor or React/Angular/Vue? Here's an honest comparison:

  • Ecosystem: JavaScript still has a larger ecosystem of UI libraries and components. But the Blazor ecosystem has grown significantly, with mature libraries like MudBlazor, Radzen, and Syncfusion covering most needs.
  • Performance: For compute-heavy tasks (data processing, encryption, image manipulation), Blazor WASM can outperform JavaScript thanks to WebAssembly. For DOM-heavy apps with thousands of interactive elements, React's virtual DOM still has an edge.
  • Developer productivity: If your team already knows C# and .NET, Blazor eliminates the context-switching cost. You ship faster with one language across the stack.
  • Hiring: In the USA and UK markets, finding .NET developers is generally easier than finding senior React developers. Blazor lets your existing backend team build the frontend.

The bottom line: Blazor vs JavaScript isn't about which is objectively better — it's about which makes your team faster. If you're a .NET shop, Blazor is the pragmatic choice.

Conclusion — Start Building with Blazor WebAssembly Today

This Blazor WebAssembly tutorial covered the core concepts you need to build production-ready C# web apps that run entirely in the browser. You learned how to create components, call APIs, manage state, optimize performance, and avoid the traps that catch most developers.

Here are the key takeaways:

  • Blazor WebAssembly lets you write interactive web apps in C# with no JavaScript dependency.
  • Components are the fundamental building block — keep them small, focused, and composable.
  • Use AOT compilation, trimming, and lazy loading to make your app fast.
  • Never put sensitive logic in the client — validate everything on the server.
  • For .NET teams, Blazor eliminates the frontend/backend language divide and accelerates delivery.

The best way to learn is to build. Take the code examples from this tutorial, extend them with your own features, and deploy your first Blazor WASM app this week. The C# skills you already have are all you need to get started.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Send an Email via SMTP with MailKit Using .NET 6

How to Send an Email in .NET Core This tutorial show you how to send an email in .NET 6.0 using the MailKit email client library. Install MailKit via NuGet Visual Studio Package Manager Console: Install-Package MailKit How to Send an HTML Email in .NET 6.0 This code sends a simple HTML email using the Gmail SMTP service. There are instructions further below on how to use a few other popular SMTP providers - Gmail, Hotmail, Office 365. // create email message var email = new MimeMessage(); email.From.Add(MailboxAddress.Parse("from_address@example.com")); email.To.Add(MailboxAddress.Parse("to_address@example.com")); email.Subject = "Email Subject"; email.Body = new TextPart(TextFormat.Html) { Text = "<h1>Test HTML Message Body</h1>" }; // send email using var smtp = new SmtpClient(); smtp.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls); smtp.Authenticate("[Username]", "[Password]"); smtp.Se...