Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notify EditContext that field has changed for Blazor validation

I am trying to understand the inner workings of Blazor (and eventually write some middleware). I have a fiddle that binds three different fields in three different ways:

fiddle

  • The FirstName field is bound to an InputText works as expected and displays the validation message when clearing the box and focus changes.
  • The MiddleName field is bound to a regular input and does not work "as expected" and displays no validation message when it is cleared and focus changes. By going thru the Blazor source, I've identified that EditContext.NotifyFieldChanged is a concern of the control itself (InputBase).
    • Worth noting that the message IS displayed if you submit the form which I would like to understand the lifecycle involved.
  • The LastName field is also bound to a regular input, but does a bit of hackery to raise EditContext.OnFieldChanged and trigger the validation to work. Is there a better way to do this for onchange? How does the submit manage to raise the validation message? There is a lot of reference capture, expressions, reflection, and use of the "internal use only" CreateBinder method.

Below is the code for the fiddle for easier reference:

@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Linq.Expressions;
@using System.Reflection;
@implements IHasEditContext;

<h1>Hello, world!</h1>

<EditForm EditContext="this.EditContextRef">
    <DataAnnotationsValidator></DataAnnotationsValidator>
    <div class="form-group">
        <InputText @bind-Value="@this.FirstName" class="form-control" />
        <ValidationMessage For="() => this.FirstName"></ValidationMessage>
    </div>
    <div class="form-group">
        <input @bind-value="@this.MiddleName" class="form-control" />
        <ValidationMessage For="() => this.MiddleName"></ValidationMessage>
    </div>
    <div class="form-group">
        <input value="@this.LastName" class="form-control" @onchange="(CreateBinder2(this, () => this.LastName,  this.LastName))" />
        <ValidationMessage For="() => this.LastName"></ValidationMessage>
    </div>


    <input type="submit" value="Go" />
</EditForm>

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        EditContextRef = new EditContext(this);
    }

    //BasicFormValidator Form1Validator = new BasicFormValidator();
    [Required]
    public String FirstName { get; set; } = "delete me and change focus to cause validation";
    [Required]
    public String MiddleName { get; set; } = "delete me and change focus - no validation";
    [Required]
    public String LastName { get; set; } = "delete me and change focus - validation but manually calling NotifyFieldChanged";

    public EditContext EditContextRef { get; set; }

    public static EventCallback<ChangeEventArgs> CreateBinder2(
        IHasEditContext receiver,
        Expression<Func<string?>> propExpression,
        string existingValue,
        System.Globalization.CultureInfo? culture = null)
    {
        var fieldIdentifier = FieldIdentifier.Create(propExpression);
        Action<String> valueSetter = (string v) =>
        {
            PropertyInfo prop = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName, BindingFlags.Public | BindingFlags.Instance);
            prop.SetValue(fieldIdentifier.Model, v);
            receiver.EditContextRef.NotifyFieldChanged(fieldIdentifier);
        };

        return EventCallback.Factory.CreateBinder<string>(receiver, valueSetter, existingValue, culture);
    }


}
like image 964
b_levitt Avatar asked Oct 28 '25 05:10

b_levitt


2 Answers

But i still don't understand how the submit did manage to raise NotifyFieldChanged

It didn't. When you Submit the EditForm then EditContext.Validate() is called and validates all fields (properties) in the Model.

I don't know the exact inner workings of Validate() but you can add this to the fiddle:

 <input type="button" value="Check" @onclick="ValidateThis" />

and

void ValidateThis()
{
    Console.WriteLine("Before " + string.Join(",", EditContextRef.GetValidationMessages()));
    EditContextRef.Validate();
    Console.WriteLine("After  " + string.Join(",", EditContextRef.GetValidationMessages()));
}

The "After" writeline will include MiddleName if that is empty.

like image 89
Henk Holterman Avatar answered Oct 30 '25 03:10

Henk Holterman


Your EditForm includes a Submit button, so when you click the button, EditForm recognises the submit action and calls it's internal HandleSubmitAsync. Even if you have no handlers attached to the EditForm, this method calls Validate on the EditContext. This will validate all validation attributed properties in your model, which in your case is the actual component (page).

There's nothing wrong with your manual OnChange handling and setting, as long as you have a good understanding of how InputBase, EditForm and EditContext work and interreact. You're just reproducing what's wrapped up in InputBase. Calling NotifyFieldChanged maintains the editstate on the EditContext. However, be aware that this is not really a true representation of the editstate, it only maintains the state against the last edit not against the original model.

As this is a demo, using the component as the Model is Ok, but you should have a proper data class for the model (you almost certainly know this already, but as others may read this answer later, ...).

like image 28
MrC aka Shaun Curtis Avatar answered Oct 30 '25 03:10

MrC aka Shaun Curtis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!