Validating an input on keypress instead of on change in Blazor

 
 
  • Gérald Barré

Blazor includes built-in support for creating forms and validating fields using data annotations.

Razor
<EditForm Model="model">
    <DataAnnotationsValidator />

    <InputText @bind-Value="model.Text" />
    <ValidationMessage For="() => model.Text" />

    <div>
        Current value: @model.Text
    </div>

    <button class="btn btn-primary" type="submit">Submit</button>
</EditForm>

@code{
    Model model = new Model();

    class Model
    {
        [StringLength(maximumLength: 10, MinimumLength = 3)]
        public string Text { get; set; }
    }
}

While this works well, validation only occurs when the input loses focus:

The <InputText> component uses the onchange event to bind the value and trigger validation. This event fires when the user commits the element's value, which for a text input means when the element loses focus. While the @bind directive lets you specify which event to use, the InputText component does not expose an alternative event. Let's try with a raw <input> element so we can use the oninput event instead:

Razor
<EditForm Model="model">
    <DataAnnotationsValidator />

    <input type="text" @bind-value="model.Text" @bind-value:event="oninput" />
    <ValidationMessage For="() => model.Text" />

    <div>
        Current value: @model.Text
    </div>

    <button class="btn btn-primary" type="submit">Submit</button>
</EditForm>

This time, the value is bound after every keystroke, but validation no longer works 😦

When using the InputText component, validation works because the component integrates with the EditContext created by EditForm and notifies it when the field value changes. When using a plain <input> element, the model value updates correctly but the EditContext is never notified, so the validation rules are never evaluated.

The solution is to create a custom component. Create a new file named Shared/InputTextOnInput.razor with the following content:

Razor
@* Inherits from the original InputText component *@
@inherits InputText

@* Bind the oninput event *@
<input @attributes="AdditionalAttributes"
       class="@CssClass"
       value="@CurrentValue"
       @oninput="EventCallback.Factory.CreateBinder<string>(this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />

Now you can use the component in the form:

Razor
<EditForm Model="model">
    <DataAnnotationsValidator />

    <InputTextOnInput @bind-Value="model.Text" />
    <ValidationMessage For="() => model.Text" />

    <div>
        Current value: @model.Text
    </div>

    <button class="btn btn-primary" type="submit">Submit</button>
</EditForm>

It now works as expected:

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?