0Pricing
C# Academy · Lesson

Data Binding & Event Handling

Apply one-way and two-way data binding, handle DOM and component events, and use EventCallback.

Data Binding & Event Handling is a free C# Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Binding in Blazor

Blazor supports three binding directions: one-way (read-only, @expr), event-based (@onclick etc.), and two-way (@bind). Together they form the foundation of reactive UIs.

One-Way Data Binding

Simply output a C# expression inside markup using @. The value updates every time the component re-renders but the DOM cannot push values back to C#.

<!-- One-way: C# -> DOM -->
<p>Count: @_count</p>
<p>Full name: @($"{_firstName} {_lastName}")</p>
<p>Price: @Price.ToString("C")</p>

@code {
    private int _count = 0;
    private string _firstName = "Alice";
    private string _lastName = "Smith";
    [Parameter] public decimal Price { get; set; }
}

Two-Way Binding with @bind

@bind wires an input element to a field using both property-setting and change events. It works with text, numbers, dates, checkboxes, and selects.

<input @bind="_name" placeholder="Enter name" />
<input type="number" @bind="_quantity" />
<input type="date" @bind="_dueDate" />
<input type="checkbox" @bind="_isActive" />

<p>Name: @_name | Qty: @_quantity | Active: @_isActive</p>

@code {
    private string _name = "";
    private int _quantity = 1;
    private DateTime _dueDate = DateTime.Today;
    private bool _isActive = true;
}

@bind with Custom Event

By default @bind uses the onchange event. Use @bind:event to change it — for example, oninput fires on every keystroke for live search.

@* Updates on every keystroke instead of on focus-loss *@
<input @bind="_search"
       @bind:event="oninput"
       placeholder="Live search..." />

<ul>
    @foreach (var item in Filtered)
    {
        <li>@item</li>
    }
</ul>

@code {
    private string _search = "";
    private string[] Items = { "Apple", "Banana", "Cherry", "Avocado" };
    private IEnumerable<string> Filtered =>
        Items.Where(i => i.Contains(_search, StringComparison.OrdinalIgnoreCase));
}

Event Handling with @onclick

Use @onclick (and other DOM event directives) to attach C# delegates to DOM events. Both synchronous methods and async tasks work.

<button @onclick="Increment">+</button>
<button @onclick="DecrementAsync">-</button>
<button @onclick="() => _count = 0">Reset</button>
<p>Count: @_count</p>

@code {
    private int _count = 0;

    private void Increment() => _count++;

    private async Task DecrementAsync()
    {
        await Task.Delay(200); // simulate async work
        _count--;
    }
}

Event Arguments

DOM events provide event argument objects. Declare the appropriate argument type in your handler to access coordinates, key codes, input values, etc.

<input @onkeydown="OnKeyDown" @onfocus="OnFocus" />
<div @onmousemove="OnMouseMove">Move here</div>
<p>@_info</p>

@code {
    private string _info = "";

    private void OnKeyDown(KeyboardEventArgs e)
        => _info = $"Key: {e.Key} (Ctrl={e.CtrlKey})";

    private void OnFocus(FocusEventArgs e)
        => _info = "Input focused";

    private void OnMouseMove(MouseEventArgs e)
        => _info = $"Mouse: ({e.ClientX}, {e.ClientY})";
}

EventCallback for Parent-Child Communication

EventCallback is the Blazor way for child-to-parent communication. Unlike a regular Action/Func, it automatically triggers StateHasChanged on the parent and handles exceptions correctly.

@* Child: ProductCard.razor *@
<button @onclick="() => OnBuy.InvokeAsync(Product)">Buy</button>

@code {
    [Parameter] public Product Product { get; set; } = default!;
    [Parameter] public EventCallback<Product> OnBuy { get; set; }
}

@* Parent: *@
<ProductCard Product="p" OnBuy="HandleBuy" />

@code {
    private async Task HandleBuy(Product p)
    {
        await CartService.AddAsync(p);
        // Parent re-renders automatically
    }
}

Preventing Default and Stopping Propagation

Use @onclick:preventDefault to cancel the browser's default action, and @onclick:stopPropagation to stop event bubbling.

@* Prevent form submission *@
<form @onsubmit="HandleSubmit" @onsubmit:preventDefault>
    <input @bind="_value" />
    <button type="submit">Save</button>
</form>

@* Stop click from bubbling to parent div *@
<div @onclick="ParentClicked">
    <button @onclick="ChildClicked"
            @onclick:stopPropagation>
        Click me
    </button>
</div>

Two-Way Binding with @bind-Value on Custom Components

Implement the Value/ValueChanged pattern on a custom component to support @bind-Value from the parent — the standard Blazor two-way binding contract.

@* RatingInput.razor *@
@for (int i = 1; i <= 5; i++)
{
    int star = i;
    <span class="@(star <= Value ? "filled" : "")"
          @onclick="() => ValueChanged.InvokeAsync(star)">★</span>
}

@code {
    [Parameter] public int Value { get; set; }
    [Parameter] public EventCallback<int> ValueChanged { get; set; }
}

@* Usage: *@
<RatingInput @bind-Value="_rating" />

Debouncing Input Events

For expensive operations triggered by user input (API calls, heavy filtering), debounce the event using a CancellationTokenSource to delay execution until the user stops typing.

@code {
    private string _query = "";
    private CancellationTokenSource? _cts;

    private async Task OnSearchInput(ChangeEventArgs e)
    {
        _query = e.Value?.ToString() ?? "";
        _cts?.Cancel();
        _cts = new CancellationTokenSource();

        try
        {
            await Task.Delay(300, _cts.Token); // wait 300ms
            Results = await SearchService.SearchAsync(_query);
        }
        catch (TaskCanceledException) { /* user still typing */ }
    }

    private List<string> Results = new();
}

Quick Check

What is the advantage of EventCallback over Action/Func for child-to-parent events in Blazor?

Recap: Data Binding & Event Handling

Key takeaways:

  • One-way binding: output C# expressions with @expr
  • Two-way binding: use @bind on inputs; @bind:event changes the trigger
  • DOM events: @onclick, @oninput, @onkeydown with typed event args
  • EventCallback<T>: child-to-parent communication with auto StateHasChanged
  • Implement Value/ValueChanged for custom component two-way binding
  • Debounce expensive operations to reduce unnecessary API calls

Frequently asked questions

Is the “Data Binding & Event Handling” lesson free?

Yes — the full text of “Data Binding & Event Handling” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Data Binding & Event Handling”?

Apply one-way and two-way data binding, handle DOM and component events, and use EventCallback. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Data Binding & Event Handling” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Blazor Component Model
  2. Data Binding & Event Handling
  3. Component Communication & DI
  4. State Management in Blazor
← Back to C# Academy