Understanding Data Binding in Blazor

Introduction to Razor Syntax

Blazor, a portmanteau of Browser and Razor, leverages the Razor templating syntax for component development. Understanding Razor is key to mastering Blazor. Razor allows embedding code within templates, enabling dynamic content generation for HTML, code, and other formats. Initially introduced with ASP.NET MVC, Razor executed server-side in ASP.NET Core MVC. In Blazor, however, Razor code runs directly in the browser (via Blazer WebAssembly), updating the UI without server roundtrips.

Component Parameters

Consider the SurveyPrompt.razor component:

// SurveyPrompt.razor

<div class="alert alert-secondary mt-4" role="alert">
   <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
   <strong>@Title</strong>
   <span class="text-nowrap">
       Please take our
       <a target="_blank" class="font-weight-bold"
          href="https://go.microsoft.com/fwlink/?linkid=2148851">brief survey</a>
   </span>
   and tell us what you think.
</div>
@code {
   // Demonstrates how a parent component can supply parameters
   [Parameter]
   public string Title { get; set; }
}

Razor components primarily consist of HTML markup. C# properties and methods can be included within the @code block. This code is compiled into a .NET class. The SurveyPrompt component accepts a Title parameter, which is set in another component, like Index.razor:

// Index.razor

<SurveyPrompt Title="How is Blazor working for you?" />

The [Parameter] attribute exposes the Title property as a component parameter. The @Title syntax in the Razor template embeds the value of the Title property into the HTML.

One-Way Data Binding

One-way data binding involves data flowing in a single direction, typically from the component to the DOM (to display data) or from the DOM to the component (in response to events).

Component to DOM Binding

Examine the Counter.razor component:

@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">
   Click me
</button>

@code {
   private int currentCount = 0;
   private void IncrementCount()
   {
   	currentCount++;
   }
}

The currentCount field is displayed using @currentCount. When the button is clicked, the Blazor runtime detects changes to currentCount and automatically updates the DOM.

Attribute Binding

HTML attributes can also be bound to component data. Consider dynamically applying CSS classes:

@page "/counter"
<h1>Counter</h1>
<p>Current count: <span class="@BackgroundColor">@currentCount</span></p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
   private int currentCount = 0;
   private void IncrementCount()
   {
   currentCount++;
   }
   private string BackgroundColor
   => (currentCount % 2 == 0) ? "red-background" : "yellow-background";
}

Here, the BackgroundColor property dynamically determines the CSS class applied to the <span> element.

Conditional Attribute Binding

Attributes like disabled can be conditionally bound to boolean expressions. If the expression evaluates to true, the attribute is applied; otherwise, it's omitted.

<button class="btn btn-primary"
       disabled="@(currentCount > 10)"
       @onclick="IncrementCount">
   Click me
</button>

The button becomes disabled when currentCount exceeds 10.

Event Handling and Data Binding

Blazor facilitates handling DOM events, such as click events, without relying on JavaScript.

Event Binding Syntax

The @on<event> syntax is used to bind evants. For a click event, use @onclick.

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

Clicking the button triggers the IncrementCount method, which in turn updates the UI.

Event Arguments

Event handler methods can accept arguments, such as MouseEventArgs, to access event-specific data.

private void IncrementCount(MouseEventArgs e)
{
   if (e.CtrlKey)
   {
       currentCount--;
   }
   else
   {
       currentCount++;
   }
}

Using C# Lambda Expressions

Event handlers can also be defined using C# lambda expressions for concise event handling.

<button class="btn btn-primary"
       disabled="@(currentCount > 10)"
       @onclick="@(() => currentCount++)">
   Click me
</button>

Two-Way Data Binding

Two-way data binding allows changes in the component to update the DOM, and modifications in the DOM to update the component.

@bind Directive

The @bind directive simplifies two-way data binding, commonly used with input elements.

<input type="number" @bind="@increment" />

This is equivalent to explicitly binding the value attribute and handling the onchange event:

<input type="number"
          value="@increment"
          @onchange="@((ChangeEventArgs e) => increment = int.Parse($"{e.Value}"))" />

Binding to Other Events: @bind:event

By default, @bind uses the onchange event. To update bindings immediately, use @bind:event="oninput".

<input type="number" @bind="@increment" @bind:event="oninput" />

Preventing Default Actions

The @:{event}:preventDefault syntax can prevent the browser's default behavior for an event.

<input type="number"
          @bind="@increment"
          @onkeypress="KeyHandler"
          @onkeypress:preventDefault="@shouldPreventDefault" />

If the value is omitted (@onkeypress:preventDefault), the default action is always prevented.

Stopping Event Propagation

Use @:{event}:stopPropagation to prevent an event from bubbling up to parent elements.

<div @onmousemove="InnerMouseMove"
        @onmousemove:stopPropagation>
       @innerPos
   </div>

Formatting Dates

The @bind:format attribute can be used to format date-time values during binding.

<input @bind="@Today" @bind:format="yyyy-MM-dd" />
@code {
	private DateTime Today { get; set; } = DateTime.Now;
}

Change Detection

Blazor automatically detects changes and re-renders the UI. However, in scenarios involving background threads or certain asynchronous operations, you might need to manually trigger a re-render using StateHasChanged().

Manual State Update

When using .NET Timers or other background operations that modify component state, call StateHasChanged() to inform Blazor to update the UI.

private void AutoIncrement()
{
   var timer = new System.Threading.Timer(
   callback: (_) => { IncrementCount(); StateHasChanged(); },
   state: null,
   dueTime: TimeSpan.FromSeconds(1),
   period: TimeSpan.FromSeconds(1));
}

Building a Pizza Ordering Application

This section details building a Blazor application for ordering pizzas, demonstrating data modeling, UI construction, and validation.

Data Models

Define C# classes to represent application data, such as Pizza, Menu, Customer, ShoppingBasket, and UI. These are typically placed in a shared project.

// Pizza.cs
namespace PizzaPlace.Shared
{
   public enum Spiciness { None, Spicy, Hot }
   public class Pizza
   {
       public Pizza(int id, string name, decimal price, Spiciness spiciness)
       {
           Id = id; Name = name; Price = price; Spiciness = spiciness;
       }
       public int Id { get; }
       public string Name { get; }
       public decimal Price { get; }
       public Spiciness Spiciness { get; }
   }
}

// Menu.cs
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Shared
{
   public class Menu { public List<Pizza> Pizzas { get; set; } = new List<Pizza>(); /* ... */ }
}

// Customer.cs
using System.ComponentModel.DataAnnotations;
namespace PizzaPlace.Shared
{
   public class Customer
   {
       public int Id { get; set; }
       [Required(ErrorMessage = "Please provide a name")]
       public string Name { get; set; } = default!;
       // ... other properties
   }
}

// ShoppingBasket.cs
using System.Collections.Generic;
namespace PizzaPlace.Shared
{
   public class ShoppingBasket
   {
       public Customer Customer { get; set; } = new Customer();
       public List<int> Orders { get; set; } = new List<int>(); /* ... */
   }
}

// State.cs
namespace PizzaPlace.Shared
{
   public class State
   {
       public Menu Menu { get; } = new Menu();
       public ShoppingBasket Basket { get; } = new ShoppingBasket();
       public decimal TotalPrice => Basket.Orders.Sum(id => Menu.GetPizza(id)!.Price);
   }
}

Displaying the Menu

Use a @foreach loop to iterate over menu items and display them. A helper method can convert enum values to image URLs.

@page "/"
@using PizzaPlace.Shared

<h1>Our selection of pizzas</h1>
@foreach (var pizza in State.Menu.Pizzas)
{
   <div class="row">
       <div class="col">@pizza.Name</div>
       <div class="col text-right">@($"{pizza.Price:0.00}")</div>
       <div class="col"><img src="@SpicinessImage(pizza.Spiciness)" alt="@pizza.Spiciness" /></div>
       <div class="col">
           <button class="btn btn-success" @onclick="@(() => AddToBasket(pizza))">Add</button>
       </div>
   </div>
}

@code {
   private State State { get; } = new State();
   protected override void OnInitialized() { /* Initialize menu */ }
   private string SpicinessImage(Spiciness spiciness) => $"images/{spiciness.ToString().ToLower()}.png";
   private void AddToBasket(Pizza pizza) => State.Basket.Add(pizza.Id);
}

Managing the Shopping Basket

The shopping basket displays selected pizzas and allows removal. Tuples are used to efficiently represent pizza and its position in the order.

@if (State.Basket.Orders.Any())
{
   <h1>Your current order</h1>
   @foreach (var (pizza, pos) in State.Basket.Orders.Select(
       (id, pos) => (State.Menu.GetPizza(id), pos)))
   {
       <div class="row mb-2">
           <div class="col">@pizza.Name</div>
           <div class="col text-right">@($"{pizza.Price:0.00}")</div>
           <div class="col">
               <button class="btn btn-danger" @onclick="@(() => RemoveFromBasket(pos))">Remove</button>
           </div>
       </div>
   }
   <div class="row">
       <div class="col"> Total: @($"{State.TotalPrice:0.00}")</div>
   </div>
}
@code {
   private void RemoveFromBasket(int pos) => State.Basket.RemoveAt(pos);
}

Customer Information and Validation

Blazor's EditForm, InputText, DataAnnotationsValidator, and ValidationMessage components facilitate form handling and validation using Data Annotations.

<EditForm Model="@State.Basket.Customer" OnValidSubmit="PlaceOrder">
   <DataAnnotationsValidator />
   <fieldset>
       <div class="mb-2 form-group form-floating">
            <InputText class="form-control" id="name" @bind-Value="@State.Basket.Customer.Name" />
            <label for="name">Name:</label>
            <ValidationMessage For="@(() => State.Basket.Customer.Name)" />
       </div>
       <!-- ... other fields ... -->
       <div class="mb-2">
            <button type="submit" class="btn btn-success">Checkout</button>
       </div>
   </fieldset>
</EditForm>

Custom CSS can be applied to provide visual feedback for validation states.

Tags: Blazor razor data-binding component-model event-handling

Posted on Mon, 11 May 2026 12:00:07 +0000 by Beatnik