MudForm Validation with MudBlazor & Fluent Validation

tl;dr

You're going to have to manually call your FluentValidation validator to ensure your view model is valid before you save any data and the validator must include the state of the entire view model, including any child and nested child view models.

In my case I want to keep the Save button enabled at all times, instead of disabling it when the values the user enters are not valid. My personal preference is not to send the user hunting for the reason they can't save their work. It always pisses me off. Let them try, then tell them what they have to do to fix it.

At the same time, I want immediate feedback directly where they are looking as they enter their data so that they can correct it while their attention is in the context of that value. So, they don't have to wait to press save to go back and fix obvious problems, like an incomplete phone number. Greedy, I know.

I'm confused. Again.

The key is that MudBlazor form validation only applies to the fields that have a validation parameter associated with them. It has no idea about an entire FluentValidation validator you created. All the form fields may pass their own validation test but the overall state of your view model (all the stuff you're trying to get ready to send somewhere) may be invalid.

And on some controls, validation isn't supported. Especially if you make your own custom controls. To be clear, there's a way to do it. I hear people talk about it, but I can't figure it out.

A hybrid solution.

Since I'm tying this to my Save button approach, it's important that this validator ensure the entire view model is valid, regardless of how I'm handling validation for fields, components and even nested child components.

You start by creating a FluentValidation validator and then adding the extra ValidateValue function as shown in the MudBlazor documentation.

To make this work, you pass a parameter called Model and another called Validation.

The important thing to note is that the Validation parameter is a function that looks like this: "@(validator.ValidateValue)". Its only purpose is to supply the default function for a field that doesn't have its own validation on it. (See the docs mentioned above.) You can specify validation for an individual form field that isn't tied to FluentValidation but I have only done that when I'm validating manually.

Then on a form field, you can specify the name of the property in the view model that applies to this field using the For parameter. Mine looks like this:


<MudForm @ref="@form" Model="@ViewModel" Validation="@(validator.ValidateValue)" ValidationDelay="0" 

<more stuff here...>

<MudTextField Label="Name"
                Value="@ViewModel.Name"
                ValueChanged="NameChanged"
                For="@(()=>ViewModel.Name)"
                T="string"
                Variant="@Variant.Outlined"
                HelperTextOnFocus="true"
                HelperText="Use a descriptive name for your account" />

Using the For parameter to force validation using the Fluent Validator

What this does is tell the form that it has a field that it can validate, and to show it with failed validation emphasis (red) and the proper message when it fails and to clear those when it passes validation. I really like the way the authors handled it and try to use it as much as possible.

This handles the cases where I want the form to show failed validation immediately after the user leaves the field.

Let's try saving it.

In whatever save logic you have you'd probably have statements like these:

await form.Validate();
if(!form.IsValid)
  {
    <show them the error of their ways>
  }
else
  {
    <save their work>
  }

Using MudBlazor form Validate only...

When form.Validate() runs, any fields failing validation will show their error messages.

But this is only going to validate the fields on the form with the For statement.

What it doesn't do is run the entire validator. If you have child components that have their own validators they won't run, form.IsValid will return true and now we have a problem.

For is not for everyone

It turns out that not all MudBlazor controls support validation, like MudChipset. Which means we can't use the built in support for our own controls, even if they are combinations of MudBlazor controls.

These could be custom controls that use a MudForm of their own, or custom controls that don't use MudForm at all.

In those cases, we'll have to use some manual validation, use MudBlazor's handy way of displaying error state, etc.

Now can we Save it?

We'll need to update our save logic to include manually calling our FluentValidation validator.

// this will light up the standard validation errors if any
await form.Validate(); 

// now validate tne *entire* model
var result = await validator.ValidateAsync(viewModel, cancellationToken)

// no need to check "form.IsValid" unless you're doing something weird.
if(!result.IsValid)
  {
    <now show them all the errors regardless of where they occurred.>
  }
else
  { 
    <off to the races>
  }
  

Validating the entire view model before saving.

In my case, I ended up writing my own ValidationSummary that appears when they try to save and there are problems. I did that so I could update that summary as they made each correction. When all the corrections are complete, I clear the summary and pop up a little toast notification telling them everything is good. Now they can press Save.