Reputation: 7415
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:
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);
}
}
Upvotes: 4
Views: 11644
Reputation: 30016
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, ...).
Upvotes: 1
Reputation: 273179
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.
Upvotes: 1
Reputation: 1746
When using the <InputText>
component you can use more complex binding super easily.
For example:
<InputText Value="@this.MiddleName"
ValueChanged="(string middleName) => MiddleNameChanged(middleName)"
ValueExpression="() => this.MiddleName" />
@code
{
private void MiddleNameChanged(string middleName)
{
this.MiddleName = middleName;
// Do whatever you want here
}
}
This works across all other built in Input Components for every datatype they support. It also can be combined with async and await.
If you want to create your own Input for other datatypes I suggest to inherit from InputBase and implement your BindConverter there. Here is an example which shows you how to bind to datetime values with the InputDate component. https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputDate.cs
Upvotes: 0