Skip to content

Blazor Tutorial for Beginners: Build Your First App Step by Step (.NET 10)

Gulab Chand Tejwani

Gulab Chand Tejwani

PMP · PL-300 · Microsoft Certified Trainer

· 12 min read

Blazor.NETC#Blazor TutorialASP.NET CoreWeb Development
Blazor Tutorial for Beginners: Build Your First App Step by Step (.NET 10)

To build your first Blazor app, install the .NET 10 SDK, run dotnet new blazor -o TaskBoard, then dotnet watch inside the folder. That gives you a working Blazor Web App with interactive server rendering. From there you add a Razor component (a .razor file with HTML and C#), give it the @rendermode InteractiveServer directive so buttons respond, and inject a C# service to hold your data. This tutorial builds a small task board app doing exactly that.

I'm Gulab Chand Tejwani, a Microsoft Certified Trainer, and I teach Blazor at TechNet Consultancy in Anand. This is the same first exercise I use in class. It takes about an hour if you type the code yourself, which I strongly recommend over copying.

What do you need before starting?

  • The .NET 10 SDK. Download it from dotnet.microsoft.com. Check it with dotnet --version; you should see a number starting with 10.
  • An editor. Visual Studio 2026 on Windows, or VS Code with the C# Dev Kit extension on any operating system.
  • Basic C#. Classes, properties, lists and methods. If those feel shaky, spend a week on C# first. My .NET developer roadmap explains what to cover.

You don't need JavaScript. That's one of the main reasons C# developers pick Blazor.

How do you create a Blazor project?

Open a terminal and run:

dotnet new blazor -o TaskBoard
cd TaskBoard
dotnet watch

dotnet new blazor creates a Blazor Web App, the template Microsoft recommends since .NET 8. By default it sets up interactive server rendering, where the UI runs on the server and updates the browser over a SignalR connection. dotnet watch starts the app and reloads it when you save a file. Your browser should open on a page with Home, Counter and Weather links.

What's inside the project?

The files you'll touch most:

  • Program.cs sets up services and the request pipeline.
  • Components/App.razor is the root HTML document.
  • Components/Routes.razor contains the router that maps URLs to pages.
  • Components/Layout/MainLayout.razor is the shared layout with the navigation menu.
  • Components/Pages/ holds your pages. Any component with an @page directive becomes a routable page.

Open Program.cs and find these two lines. They're what turn on interactive server components:

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

How does a Blazor component work?

Open Components/Pages/Counter.razor. It's short:

@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Four ideas in nine lines:

  1. @page "/counter" makes it a page at that URL.
  2. @rendermode InteractiveServer makes it interactive. Remove that line and the button stops working, because the page is then rendered statically on the server with no live connection. This is the most common beginner surprise in .NET 8 and later.
  3. @currentCount writes a C# value into the HTML.
  4. @onclick wires a button to a C# method. After the method runs, Blazor re-renders the component and sends only the changes to the browser.
Five steps from URL to re-render in an interactive Blazor component
What happens between a click and the updated page in an Interactive Server component.

The Razor components docs on Microsoft Learn cover this model in detail.

Step 1: Create a model and a service

Make a folder called Models and add TaskItem.cs:

using System.ComponentModel.DataAnnotations;

namespace TaskBoard.Models;

public class TaskItem
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Give the task a title.")]
    [StringLength(80, ErrorMessage = "Keep it under 80 characters.")]
    public string Title { get; set; } = "";

    public bool IsDone { get; set; }
}

Now a Services folder with TaskService.cs. For this tutorial it keeps tasks in memory. Later you'd swap it for a database with EF Core.

using TaskBoard.Models;

namespace TaskBoard.Services;

public class TaskService
{
    private readonly List<TaskItem> _tasks = new();
    private int _nextId = 1;

    public IReadOnlyList<TaskItem> GetAll() => _tasks;

    public void Add(string title)
    {
        _tasks.Add(new TaskItem { Id = _nextId++, Title = title.Trim() });
    }

    public void Toggle(int id)
    {
        var task = _tasks.FirstOrDefault(t => t.Id == id);
        if (task is not null) task.IsDone = !task.IsDone;
    }

    public void Remove(int id) => _tasks.RemoveAll(t => t.Id == id);
}

Register it in Program.cs, before var app = builder.Build();:

builder.Services.AddScoped<TaskBoard.Services.TaskService>();

Why AddScoped? In interactive server apps, a scoped service lives for one user's circuit (their connection), so each browser tab gets its own task list. AddSingleton would share one list across every user, which is rarely what you want for user data.

LifetimeLives forUse it for
AddScopedOne user's circuit (browser tab)Per-user state, data services, most of your app
AddSingletonThe whole app, every userShared caches, configuration, stateless helpers
AddTransientEach time it is injectedLightweight, stateless services
Service lifetimes in an Interactive Server Blazor app

Step 2: Build the task page

Create Components/Pages/Tasks.razor:

@page "/tasks"
@rendermode InteractiveServer
@using TaskBoard.Models
@using TaskBoard.Services
@inject TaskService Tasks

<PageTitle>Task board</PageTitle>

<h1>Task board</h1>

<EditForm Model="newTask" OnValidSubmit="AddTask" FormName="add-task">
    <DataAnnotationsValidator />
    <div class="d-flex gap-2">
        <InputText @bind-Value="newTask.Title" class="form-control"
                   placeholder="What needs doing?" />
        <button type="submit" class="btn btn-primary">Add</button>
    </div>
    <ValidationMessage For="() => newTask.Title" />
</EditForm>

<p class="mt-3">@Tasks.GetAll().Count(t => !t.IsDone) open of @Tasks.GetAll().Count</p>

<ul class="list-group">
    @foreach (var task in Tasks.GetAll())
    {
        <li class="list-group-item d-flex justify-content-between">
            <label>
                <input type="checkbox" checked="@task.IsDone"
                       @onchange="() => Tasks.Toggle(task.Id)" />
                <span style="@(task.IsDone ? "text-decoration: line-through" : "")">
                    @task.Title
                </span>
            </label>
            <button class="btn btn-sm btn-outline-danger"
                    @onclick="() => Tasks.Remove(task.Id)">Delete</button>
        </li>
    }
</ul>

@code {
    private TaskItem newTask = new();

    private void AddTask()
    {
        Tasks.Add(newTask.Title);
        newTask = new TaskItem();
    }
}

Go to /tasks. Type a title and press Add. Try submitting an empty title and you'll see the validation message from the [Required] attribute.

What's happening here:

  • @inject TaskService Tasks asks dependency injection for the service you registered.
  • EditForm with DataAnnotationsValidator reads the attributes on TaskItem and blocks invalid submits. OnValidSubmit only runs when the model is valid.
  • @bind-Value is two-way binding: typing updates newTask.Title, and resetting newTask clears the box.
  • The lambdas in @onchange and @onclick pass the task's ID to the service.

Step 3: Add the page to the menu

Open Components/Layout/NavMenu.razor and copy one of the existing NavLink blocks:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="tasks">
        <span class="bi bi-list-check" aria-hidden="true"></span> Tasks
    </NavLink>
</div>

NavLink adds an active CSS class when the URL matches, so the menu highlights the current page without any extra code.

Step 4: Split it into a reusable component

Pages get long quickly. Pull the list item into its own component. Create Components/TaskRow.razor:

@using TaskBoard.Models

<li class="list-group-item d-flex justify-content-between">
    <label>
        <input type="checkbox" checked="@Item.IsDone" @onchange="() => OnToggle.InvokeAsync(Item.Id)" />
        @Item.Title
    </label>
    <button class="btn btn-sm btn-outline-danger" @onclick="() => OnDelete.InvokeAsync(Item.Id)">Delete</button>
</li>

@code {
    [Parameter, EditorRequired] public TaskItem Item { get; set; } = default!;
    [Parameter] public EventCallback<int> OnToggle { get; set; }
    [Parameter] public EventCallback<int> OnDelete { get; set; }
}

Then the loop in Tasks.razor becomes:

@foreach (var task in Tasks.GetAll())
{
    <TaskRow Item="task" OnToggle="Tasks.Toggle" OnDelete="Tasks.Remove" />
}

[Parameter] properties are how a parent passes data down. EventCallback is how a child tells its parent something happened. That parent-child pattern is most of what you need for real Blazor UIs.

Step 5: Load data asynchronously

Real data comes from a database or an API, and that takes time. Blazor components have lifecycle methods for this. The one you'll use most is OnInitializedAsync, which runs when the component first loads.

Add an async method to TaskService that pretends to fetch from a server:

public async Task<IReadOnlyList<TaskItem>> LoadAsync()
{
    await Task.Delay(800); // stands in for a database or API call
    if (_tasks.Count == 0)
    {
        Add("Install the .NET 10 SDK");
        Add("Finish the Blazor tutorial");
    }
    return _tasks;
}

Then, in Tasks.razor, load it and show a message while it's working:

@if (items is null)
{
    <p><em>Loading tasks…</em></p>
}
else
{
    <ul class="list-group">
        @foreach (var task in items)
        {
            <TaskRow Item="task" OnToggle="Tasks.Toggle" OnDelete="Tasks.Remove" />
        }
    </ul>
}

@code {
    private IReadOnlyList<TaskItem>? items;

    protected override async Task OnInitializedAsync()
    {
        items = await Tasks.LoadAsync();
    }
}

Blazor renders once with items still null (so the user sees "Loading tasks…"), then renders again when the task finishes. Always handle that first, empty render. Forgetting it is the classic NullReferenceException in beginners' Blazor code.

Step 6: Fix the errors every beginner hits

You'll meet at least two of these in your first week. Here's what they mean.

  • The button does nothing. The component has no interactive render mode. Add @rendermode InteractiveServer.
  • "Cannot provide a value for property 'Tasks'". You injected a service you never registered. Add it in Program.cs.
  • NullReferenceException on first render. Data loaded in OnInitializedAsync isn't there yet. Check for null and show a loading state.
  • The form submits but nothing validates. You forgot <DataAnnotationsValidator /> inside the EditForm.
  • Changes don't appear after an external update. If something outside Blazor's event handlers changes state, such as a timer, call StateHasChanged() (or InvokeAsync(StateHasChanged) from another thread).

When you're stuck, read the browser console and the terminal running dotnet watch. Blazor's error messages are usually specific about what went wrong.

Which render mode should you use?

For this tutorial, Interactive Server is the right choice: it starts fast, keeps code on the server and needs no API layer. As your apps grow, you'll choose per page or per component.

Four Blazor Web App render modes: static, Interactive Server, Interactive WebAssembly, Interactive Auto
The four render modes in a Blazor Web App. You can mix them in one app.

The real decision for most apps is Server vs WebAssembly:

Comparison of Interactive Server and Interactive WebAssembly strengths and costs
Neither is better in general. Internal tools often suit Server; offline-friendly or high-traffic public apps often suit WebAssembly.

A rule of thumb I give in class: start with Server for internal business apps on a reliable network. Consider WebAssembly or Auto when users are many, spread out, or need parts of the app to keep working with a weak connection.

What should you learn after this tutorial?

In roughly this order:

  1. Lifecycle methods, especially OnInitializedAsync for loading data.
  2. Calling a real API with HttpClient. Pair this with our ASP.NET Core Web API tutorial.
  3. A database through EF Core. See EF Core basics with examples.
  4. Render modes. When to use Interactive WebAssembly or Auto instead of Server. Read how to implement Blazor Web App render modes.
  5. Authentication with ASP.NET Core Identity and AuthorizeView.

For a bigger picture of building production apps, our guide on developing robust Blazor web applications is the next read.

Want guided Blazor training?

Our .NET Blazor training takes this further over 40 hours: components, state, APIs, authentication, testing and deployment, with code reviews on every module. You can join live online or in the classroom in Vallabh Vidyanagar, Anand. And if your company needs a Blazor application built, our team does that too: see software and web development.

FAQ

Is Blazor good for beginners?

Yes, if you already know a little C#. You build web pages without learning JavaScript first, and the component model is easy to reason about. Complete programming beginners should learn basic C# before starting Blazor.

Why doesn't my Blazor button do anything?

Most often the component has no interactive render mode. In a Blazor Web App, pages are rendered statically by default. Add @rendermode InteractiveServer at the top of the component, or set interactivity globally when you create the project.

What's the difference between Blazor Server and Blazor WebAssembly?

Interactive Server runs your C# on the server and sends UI updates over SignalR. Interactive WebAssembly downloads the .NET runtime and your code to the browser and runs it there. Server starts faster and keeps code private; WebAssembly works offline and reduces server load. Our comparison post goes deeper.

Which version of .NET should I use for this tutorial?

.NET 10, the current Long Term Support release. The code also works on .NET 8 and on the .NET 11 release, though .NET 8 reaches end of support on 10 November 2026.

Can I use Bootstrap with Blazor?

Yes. The Blazor Web App template already includes Bootstrap, which is why the classes in this tutorial work without setup. You can replace it with any CSS framework or a Blazor component library.

Choose optional cookies

We use necessary cookies for account security. With your permission, we also use analytics and advertising cookies on marketing pages. See our Privacy Policy.