Blazor form validation

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.

Blazor guestbook form is invalid

This is the form with successful validation.

Blazor guestbook form is valid

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.

Blazor guestbook: button is disabled if form is invalid

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.

Liked this post? Empower your friends by sharing it!

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    7 thoughts on “Blazor form validation

    • September 26, 2019 at 10:18 am
      Permalink

      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` ?

    • September 26, 2019 at 12:04 pm
      Permalink

      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 :)

    • Pingback:Data annotations object graph validation is coming to Blazor

    • November 19, 2019 at 9:20 am
      Permalink

      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.

    • November 19, 2019 at 10:14 am
      Permalink

      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?

    • November 19, 2019 at 10:31 am
      Permalink

      Sorry, just realized this was due to using Radzen numeric fields, when trying out the built-in InputNumber field it does seem to work!

    • November 20, 2019 at 12:27 am
      Permalink

      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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *