Still running into problems utlizing OnChange event in Blazor

Falanga, Rod, DOH 530 Reputation points
2025-11-19T22:30:12.36+00:00

Earlier I asked The OnChanged event isn't firing for InputNumber which I got an answer for. But I've run into more problems, which irritates me. Here's some code snippets from the Blazor I'm I'm writing using .NET 9.

In a Blazor component I've written I have this for the EditForm:

<EditForm Model="@editContext" OnValidSubmit="HandleValidSubmit">

And lower in the same component I have this for an InputSelect, which is one of the Blazor inputs which isn't causing the OnChange event to fire:

    <div class="row align-items-center mb-1">
        <label class="col-3 col-form-label">Task Description:</label>
        <div class="col-9">
            <InputSelect @bind-Value="SelectedTaskId" class="form-select form-select-sm" id="selectOption">
                <option value="">Select a Task Description</option>
                @foreach (var task in tasks)
                {
                    <option value="@task.Taskid">@task.Desc</option>
                }
            </InputSelect>
        </div>
    </div>

Then in the code behind file I have this declaration:

private EditContext editContext => new EditContext(timetrackDTO);

public int SelectedTaskId { get; set; }

Lower down in the code-behind I have this in the OnInitializedAsync:

editContext.OnFieldChanged += HandleFieldChanged;

Outside of the OnInitializedAsync method I have this:

        private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
        {
            var fieldName = e.FieldIdentifier.FieldName;
            var fieldValue = e.FieldIdentifier.Model.GetType()
                .GetProperty(e.FieldIdentifier.FieldName)?
                .GetValue(e.FieldIdentifier.Model);
            Console.WriteLine($"Field '{fieldName}' changed to: {fieldValue}");   // Log the new value
            StateHasChanged();
        }

It is in the HandleFieldChanged method which never gets called when I change any value in the InputSelect for the SelectedTaskId. I don't understand why. I've tried duplicating the code I learned from my previous question but am getting stopped here.

Developer technologies | ASP.NET | ASP.NET Core
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. Jack Dang (WICLOUD CORPORATION) 5,795 Reputation points Microsoft External Staff Moderator
    2025-11-20T04:46:26.2433333+00:00

    Hi @Falanga, Rod, DOH ,

    Thanks for reaching out.

    I reproduced your issue locally. The reason your HandleFieldChanged method wasn’t firing is because of how EditContext works in Blazor:

    • EditContext must be created once and reused; otherwise, events won’t trigger properly.
    • EditContext only tracks properties inside the model. Since SelectedTaskId is now part of timetrackDTO, changes are tracked correctly.
    • Subscribing to OnFieldChanged on the single instance allows you to react to changes in real time.

    Here’s a working solution:

    @page "/timetrack"
    @using System.ComponentModel.DataAnnotations
    
    <EditForm EditContext="@editContext" OnValidSubmit="HandleValidSubmit">
        <DataAnnotationsValidator />
        <ValidationSummary />
        <div class="row align-items-center mb-1">
            <label class="col-3 col-form-label">Task Description:</label>
            <div class="col-9">
                <InputSelect @bind-Value="timetrackDTO.SelectedTaskId" class="form-select form-select-sm">
                    <option value="">Select a Task Description</option>
                    @foreach (var task in tasks)
                    {
                        <option value="@task.Taskid">@task.Desc</option>
                    }
                </InputSelect>
            </div>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </EditForm>
    
    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.Forms;
    using System.ComponentModel.DataAnnotations;
    namespace TimeTrackTest.Pages
    {
        public partial class TimeTrackComponent : ComponentBase
        {
            private EditContext? editContext;
            // Model tracked by EditContext
            private TimeTrackDTO timetrackDTO = new TimeTrackDTO();
            // Sample tasks
            private List<TaskItem> tasks = new List<TaskItem>
            {
                new TaskItem { Taskid = 1, Desc = "Task A" },
                new TaskItem { Taskid = 2, Desc = "Task B" },
                new TaskItem { Taskid = 3, Desc = "Task C" },
            };
    
            protected override void OnInitialized()
            {
                // Initialize a single EditContext instance and wire up the event
                editContext = new EditContext(timetrackDTO);
                editContext.OnFieldChanged += HandleFieldChanged;
            }
    
            private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
            {
                var fieldName = e.FieldIdentifier.FieldName;
                var fieldValue = e.FieldIdentifier.Model.GetType()
                    .GetProperty(e.FieldIdentifier.FieldName)?
                    .GetValue(e.FieldIdentifier.Model);
                Console.WriteLine($"Field '{fieldName}' changed to: {fieldValue}");
                StateHasChanged();
            }
    
            private void HandleValidSubmit()
            {
                Console.WriteLine("Form submitted successfully!");
            }
    
            // DTO model
            public class TimeTrackDTO
            {
                [Required]
                public int SelectedTaskId { get; set; }
            }
    
            // Sample Task item
            public class TaskItem
            {
                public int Taskid { get; set; }
                public string Desc { get; set; } = string.Empty;
            }
        }
    }
    

    Hope this helps! If my answer was helpful - kindly follow the instructions here so others with the same problem can benefit as well.

    1 person found this answer helpful.
    0 comments No comments

  2. Q&A Assist
    2025-11-19T22:30:32.1+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    It looks like you are trying to handle changes in an InputSelect component using the OnFieldChanged event of the EditContext, but it's not firing as expected. Here are a few things to check that might help resolve the issue:

    1. Ensure Binding is Correct: Make sure that the @bind-Value is correctly set to the property SelectedTaskId. This should allow the EditContext to track changes to this property.
    2. EditContext Initialization: You are creating a new EditContext each time the editContext property is accessed. This means that the OnFieldChanged event handler is being attached to a new instance of EditContext every time, which won't work as expected. Instead, you should create the EditContext once and store it in a field. Example:
         private EditContext editContext;
         
         protected override void OnInitialized()
         {
             editContext = new EditContext(timetrackDTO);
             editContext.OnFieldChanged += HandleFieldChanged;
         }
      
    3. Check for State Changes: Ensure that the StateHasChanged() method is being called appropriately. It should be called after the state changes to ensure the UI updates accordingly.
    4. Debugging: Add some debugging statements in the HandleFieldChanged method to check if it’s being hit at all. You can also log the fieldName to see if it matches the expected value.
    5. Component Lifecycle: Make sure that the OnInitializedAsync method is not being called multiple times unexpectedly, which could lead to multiple subscriptions to the OnFieldChanged event.

    By addressing these points, you should be able to get the OnFieldChanged event to fire correctly when the selection in the InputSelect changes.

    0 comments No comments

  3. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

  4. can kucukgultekin 330 Reputation points
    2025-11-19T23:33:52.5233333+00:00

    Hi Falanga, Rod, DOH,

    the problem this time isnt that Blazor refuses to raise events, its how the EditContext is being created and wired up.

    This line is the key: private EditContext editContext => new EditContext(timetrackDTO); - Thats not a field, its an expression-bodied property. Which means every time you read editContext, you get a brand new EditContext instance.

    So what happens is: when you write <EditForm Model="@editContext">, one EditContext instance is created and used by EditForm. When you do editContext.OnFieldChanged += HandleFieldChanged;'in OnInitializedAsync, a different EditContext instance is created and you subscribe to that one. The EditContext that EditForm actually cascades down to InputSelect isnt the same instance youre attaching OnFieldChanged to. Thats why HandleFieldChanged never fires: the field changes are being notified on a different context.

    Theres a second subtle issue here: <EditForm Model="@editContext" OnValidSubmit="HandleValidSubmit"> - EditForm has two ways of working: you pass a Model="someObject" and it internally creates new EditContext(someObject), or you pass an EditContext="editContext" and it uses that context as-is. In your case youre passing an EditContext object into the Model parameter, so EditForm uses it directly but the property getter creates new instances every time.

    The fix is to first make editContext a field and create it once:

    private EditContext editContext;
    protected override void OnInitialized()
    {
        editContext = new EditContext(timetrackDTO);
        editContext.OnFieldChanged += HandleFieldChanged;
    }
    

    Then use the EditContext parameter on EditForm instead of Model:

    <EditForm EditContext="@editContext" OnValidSubmit="HandleValidSubmit">
    

    But theres a critical point here honestly: EditContext only tracks properties of its own model (timetrackDTO). So if SelectedTaskId stays as a component-level property, EditContext never sees it and OnFieldChanged wont ever fire. This means you have two options:

    First option is moving SelectedTaskId into the timetrackDTO model:

    // Inside timetrackDTO class
    public int SelectedTaskId { get; set; }
    
    

    Then in InputSelect:

    <InputSelect @bind-Value="timetrackDTO.SelectedTaskId"
                 class="form-select form-select-sm">
        <option value="">Select a Task Description</option>
        @foreach (var task in tasks)
        {
            <option value="@task.Taskid">@task.Desc</option>
        }
    </InputSelect>
    

    This way EditContext tracks it and HandleFieldChanged gets called.

    Second option is using @bind-Value:after without depending on

    EditContext at all:

    <InputSelect @bind-Value="SelectedTaskId"
                 @bind-Value:after="OnTaskChanged"
                 class="form-select form-select-sm">
        <option value="">Select a Task Description</option>
        @foreach (var task in tasks)
        {
            <option value="@task.Taskid">@task.Desc</option>
        }
    </InputSelect>
    
    

    In code-behind:

    public int SelectedTaskId { get; set; }
    private void OnTaskChanged()
    {
        Console.WriteLine($"Task changed to: {SelectedTaskId}");
        StateHasChanged();
    }
    
    

    Your HandleFieldChanged method can stay as it is but will only work for properties inside the model. EditContext doesnt fire events for component-level properties, thats a Blazor architectural limitation.


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.