Client-side Blazor supports DataAnnotations form validation out-of-box. It’s simple and intuitive but also very flexible – if needed we can use the same mechanism to replace DataAnnotations validation with some other validation component. This blog post introduces form validation in Blazor applications and peeks also into engine of validation mechanism.
NB! Form validation in Blazor is experimental and subject to changes. This blog post is written using .NET Core 3.0 Preview 7.
Data annotations validation
Blazor supports DataAnnotations validation out-of-box. To use validation we have to have model with data annotations and edit form defined in Blazor view.
Let’s create simple model for guestbook. It has properties for poster name and message.
public class GuestbookEntry
{
[Required]
[MaxLength(10)]
public string Name { get; set; }
[Required]
public string Text { get; set; }
}
Here is Blazor form that uses out-of-box form validation. I followed same pattern with validation messages that should be familiar at least those who know ASP.NET MVC.
@page "/"
@using BlazorFormValidation.Models
<h1>My guestbook</h1>
<p>Leave me a message if you like my site</p>
<EditForm Model="@Model" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit">
<div class="alert @StatusClass">@StatusMessage</div>
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="name">Name: </label>
<InputText Id="name" Class="form-control" @bind-Value="@Model.Name"></InputText>
<ValidationMessage For="@(() => Model.Name)" />
</div>
<div class="form-group">
<label for="body">Text: </label>
<InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>
<ValidationMessage For="@(() => Model.Text)" />
</div>
<button type="submit">Ok</button>
</EditForm>
@code
{
private string StatusMessage;
private string StatusClass;
private GuestbookEntry Model = new GuestbookEntry();
protected void HandleValidSubmit()
{
StatusClass = "alert-info";
StatusMessage = DateTime.Now + " Handle valid submit";
}
protected void HandleInvalidSubmit()
{
StatusClass = "alert-danger";
StatusMessage = DateTime.Now + " Handle invalid submit";
}
}
EditForm is Blazor container for forms. Validator is added like any other Blazor component. Page above uses DataAnnotationValidator but you can create custom one if you like.
Here is the screenshot demonstrating form when fields are empty and Ok button is clicked.
This is the form with successful validation.
I think most of developers will be okay with this solution and I can finish this writing. Or no?
Disable button until form is valid
We can actually dig deeper and try something fancy. What if we want Ok button to be disabled while form is invalid? I found blog post Disabling the Submit button in Blazor Validation by Peter Himschoot where he provider solution through custom InputWatcher.
My solution is smaller. We can assign to EditForm either Model or EditContext but not both at same time. When using EditContext we have way more control over validation. This way I solved the need for additional component.
I solved button disabled status problem with simple but not so straightforward trick. Instead of boolean true-false value for disabled attribute I went with string. Value is either “disabled” or null. Notice that empty string leaves “disabled” attribute on button. I had to listen to field change event just like Peter did.
@page "/"
@using BlazorFormValidation.Models
<h1>My guestbook</h1>
<p>Leave me a message if you like my site</p>
<EditForm EditContext="@EditContext">
<DataAnnotationsValidator />
<div class="form-group">
<label for="name">Name: </label>
<InputText Id="name" Class="form-control" @bind-Value="@Model.Name"></InputText>
<ValidationMessage For="@(() => Model.Name)" />
</div>
<div class="form-group">
<label for="body">Text: </label>
<InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>
<ValidationMessage For="@(() => Model.Text)" />
</div>
<button type="submit" disabled="@OkayDisabled">Ok</button>
</EditForm>
@code
{
private EditContext EditContext;
private GuestbookEntry Model = new GuestbookEntry();
protected string OkayDisabled { get; set; } = "disabled";
protected override void OnInit()
{
EditContext = new EditContext(Model);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
base.OnInit();
}
protected override void OnAfterRender()
{
base.OnAfterRender();
SetOkDisabledStatus();
}
private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
{
SetOkDisabledStatus();
}
private void SetOkDisabledStatus()
{
if(EditContext.Validate())
{
OkayDisabled = null;
}
else
{
OkayDisabled = "disabled";
}
}
}
This is how guestbook opens when I run it. Validation messages are shown for fields and Ok button is disabled.
With EditContext class we were able to take form under our control.
Inside EditContext class
There’s more about EditContext. Take a look at EditContext class definiton.
public sealed class EditContext
{
public EditContext(object model);
public object Model { get; }
public event EventHandler<FieldChangedEventArgs> OnFieldChanged;
public event EventHandler<ValidationRequestedEventArgs> OnValidationRequested;
public event EventHandler<ValidationStateChangedEventArgs> OnValidationStateChanged;
public FieldIdentifier Field(string fieldName);
public IEnumerable<string> GetValidationMessages();
public IEnumerable<string> GetValidationMessages(FieldIdentifier fieldIdentifier);
public bool IsModified();
public bool IsModified(in FieldIdentifier fieldIdentifier);
public void MarkAsUnmodified(in FieldIdentifier fieldIdentifier);
public void MarkAsUnmodified();
public void NotifyFieldChanged(in FieldIdentifier fieldIdentifier);
public void NotifyValidationStateChanged();
public bool Validate();
}
It has events that are triggered for changes. We have methods to mark fields as unmodified, it’s easy to get field ID used bu Blazor and notify EditContext of changes if we need.
Check validation status when field changes
There’s one more minor thing to solve on our form. When form gets valid I want Ok button to be enabled at same moment and not when I leave the field. This functionality would be great for fields validated by regular expression, by example.
After some research and hacking I came out with ugly solution using KeyUp event and reflection (I’m sure Blazor guys don’t want you to do it at home). But here my solution is.
@page "/"
@using BlazorFormValidation.Models
<h1>My guestbook</h1>
<p>Leave me a message if you like my site</p>
<EditForm EditContext="@EditContext">
<DataAnnotationsValidator />
<div class="form-group">
<label for="name">Name: </label>
<InputText Id="name" Class="form-control" @bind-Value="@Model.Name" onkeyup='@(e => KeyUp(e, "Name"))'></InputText>
<ValidationMessage For="@(() => Model.Name)" />
</div>
<div class="form-group">
<label for="body">Text: </label>
<InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text" onkeyup='@(e => KeyUp(e, "Text"))'></InputTextArea>
<ValidationMessage For="@(() => Model.Text)" />
</div>
<button type="submit" disabled="@OkayDisabled">Ok</button>
</EditForm>
@code
{
private EditContext EditContext;
private GuestbookEntry Model = new GuestbookEntry();
protected string OkayDisabled { get; set; } = "disabled";
protected override void OnInit()
{
EditContext = new EditContext(Model);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
base.OnInit();
}
protected override void OnAfterRender()
{
base.OnAfterRender();
SetOkDisabledStatus();
}
private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
{
SetOkDisabledStatus();
}
void KeyUp(UIKeyboardEventArgs e, string memberName)
{
var property = Model.GetType().GetProperty(memberName);
var value = property.GetValue(Model);
property.SetValue(Model, value + e.Key);
var id = EditContext.Field(memberName);
EditContext.NotifyFieldChanged(id);
}
private void SetOkDisabledStatus()
{
if (EditContext.Validate())
{
OkayDisabled = null;
}
else
{
OkayDisabled = "disabled";
}
}
}
It’s a small thing but users will love it. We just made our form even more responsive.
Using other validators
I found FluentValidation support for Blazor by Chris Sainty. FluentValidation is popular validation library that supports also advanced validation scenarios.
With FluentValidation we use validator classes like the one shown here.
public class GuestbookEntryValidator : AbstractValidator<GuestbookEntry>
{
public GuestbookEntryValidator()
{
RuleFor(e => e.Name)
.NotEmpty().WithMessage("Name is required")
.MaximumLength(10).WithMessage("10 characters maximum");
RuleFor(e => e.Text)
.NotEmpty().WithMessage("Text is required");
}
}
Our form doesn’t change much. We just have to change validator component.
<EditForm EditContext="@EditContext">
<FluentValidationValidator />
<div class="form-group">
<label for="name">Name: </label>
<InputText Id="name" Class="form-control" @bind-Value="@Model.Name"></InputText>
<ValidationMessage For="@(() => Model.Name)" />
</div>
<div class="form-group">
<label for="body">Text: </label>
<InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>
<ValidationMessage For="@(() => Model.Text)" />
</div>
<button type="submit" disabled="@OkayDisabled">Ok</button>
</EditForm>
Now we can use full power of FluentValidator on our forms. Check out FluentValidationValidator souce code from Github repository chrissainty/FluentValidationWithRazorComponents.
Wrapping up
There’s two ways how to go with validation on Blazor. We can use Model property of EditForm and keep things simple for us. If we need more control over validation and UI then we can provide EditContext to EditForm. Those who want to go deep to internals can see how Christ Sainty integrated FluentValidation to Blazor. Although there can be changes to form validation over coming releases of Blazor we can still start building our own advanced components to use with Blazor applications.
View Comments (10)
Can you update this article to the latest Blazor 3.0.0 version?
* the `OnInit()` has been renamed to `OnInitialized()`.
* the `OnAfterRender` now has a boolean parameter
* the `UIKeyboardEventArgs` has been renamed to `KeyboardEventArgs`
* the onkeyup functionality does not work anymore, because the `e.Key` value is a string named "Backspace" or "Delete" when pressing these keys. An alternative to use would be `@onchange` ?
Hi!
Yes, I will update all the articles to Blazor 3.0.0. It takes a little time as I have also bunch of code samples at Github to update :)
Thanks for a thorough article!
I am not getting this to work though since calling SetOkDisabledStatus() in the overridden OnAfterRender method, will not trigger a new rerendering (e.g. making the button disabled when validation fails), as the render has already been made, just as the method name suggests.
This means that the render will in practice always look at the previus state - we are getting the enabling state based upon the previous state always. I am using a numeric field, but believe it is exactly the same with any text field.
This blog post was written when newest version of Blazor was Preview7. If you are using newer version then it's possible that the code doesn't work like it worked with Preview7. Also it's worth to mention that I wrote this code for client-side Blazor.
Which version of Blazor are you using?
Sorry, just realized this was due to using Radzen numeric fields, when trying out the built-in InputNumber field it does seem to work!
Oh, yes... Current implementation of Blazor uses those special form fields. If you are using custom field controls and they don't implement form field features then problems may occur.
Hi,
to resolve onkeyup problem, i have used oninput event instead onkeyup and in the event handler I have used this code:
void OnInput (ChangeEventArgs e, string memberName)
{
var property = Model.GetType().GetProperty(memberName);
var value = property.GetValue(Model);
property.SetValue(Model, e.Value );
var id = EditContext.Field(memberName);
EditContext.NotifyFieldChanged(id);
}
It seems to work fine
Thanks for your work
The first solution always takes to clicks on the form submit button before it changes state and message from on to the other after the first display. StateChanged() doesn't solve the problem.
Did a test and it showed that the first time i click the submit button it only re-validates the input and does not send the form so I suppose that is how it should work in Blazor.
Only problem is that it looks strange if you have successfully submitted it first and then get a validation error and validation error message for the field the second time and the success bootstrap alert is still shown:-/
Thanks for the code examples. I would like to see how we can have a button in the form that does not perform validation. In asp.net we could use CausesValidation="False" but I have not seen that in Blazor and I use it a lot in my dev.