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:
- 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 if 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);
}
}
