In our Forms and Form Validation in Blazor article, we have learned how to validate a form using a non-complex model. But what about a complex model validation, is it the same process? Well, in this article, we are going to answer that question. We will learn how to implement complex model validation in Blazor and also how to apply a compare validation. Both of these validation processes require a bit different logic comparing to what we learned in the mentioned article.

To download the source code for this article, you can visit the Complex Model Validation repository. There you will find two folders (start and finish) for both projects (started and finished).

We are going to divide this article into the following sections:

Let’s start.

Inspecting the Starting Project with Form Validation Prepared

In the start folder, we are going to find a starting project for this article with a basic validation implemented following the steps from the mentioned article – Forms and Form Validation in Blazor.

Let’s just inspect the project so it could be easier to follow along with the rest of the article:

Blazor Complex Model Validation Initial Project

We can see two projects – ComplexModelValidation and Shared. In the Shared project, we have the Product model class and installed System.ComponentModel.Annotations library to support our form validation process:

public class Product
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    [Required(ErrorMessage = "Supplier is required")]
    public string Supplier { get; set; }
}

We have added a reference from the Shared project to the main project, modified the Index.razor file, and added the Index.razor.cs partial class:

@page "/"

<EditForm Model="_product" OnValidSubmit="Submit" style="width:600px;">
    <DataAnnotationsValidator />
    <div class="form-group row">
        <label for="name" class="col-md-2 col-form-label">Name:</label>
        <div class="col-md-10">
            <InputText id="name" class="form-control" @bind-Value="_product.Name" />
            <ValidationMessage For="@(() => _product.Name)" />
        </div>
    </div>

    <div class="form-group row">
        <label for="supplier" class="col-md-2 col-form-label">Supplier:</label>
        <div class="col-md-10">
            <InputText id="supplier" class="form-control" @bind-Value="_product.Supplier" />
            <ValidationMessage For="@(() => _product.Supplier)" />
        </div>
    </div>

    <div class="row">
        <div class="col-md-12 text-right">
            <button type="submit" class="btn btn-success">Submit</button>
        </div>
    </div>
</EditForm>
public partial class Index
{
    private Product _product = new Product();

    private void Submit() => Console.WriteLine($"{_product.Name}, {_product.Supplier}");
}

If you start the app and click the submit button, you are going to see that the validation works:

Basic validation works in Blazor WebAssembly

So, everything works fine, but what about a complex model validation?

Complex Model Validation in Blazor

As you can see, we have a simple Product model and with it, our validation works without a problem. But let’s see, what is going to happen if we modify our Product class to be a complex model.

For that, we are going to create another class in the Shared folder:

public class ProductDetails
{
    [Required(ErrorMessage = "Description is required")]
    public string Description { get; set; }
}

For our example, this is going to be just enough.

Now, we can modify the Product class:

public class Product
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    [Required(ErrorMessage = "Supplier is required")]
    public string Supplier { get; set; }
    public ProductDetails ProductDetails { get; set; }
}

Finally, let’s modify the Index.razor file, to include the input field for this new property:

@page "/"

<EditForm Model="_product" OnValidSubmit="Submit" style="width:600px;">
    <DataAnnotationsValidator />
    <div class="form-group row">
        <label for="name" class="col-md-2 col-form-label">Name:</label>
        <div class="col-md-10">
            <InputText id="name" class="form-control" @bind-Value="_product.Name" />
            <ValidationMessage For="@(() => _product.Name)" />
        </div>
    </div>

    <div class="form-group row">
        <label for="supplier" class="col-md-2 col-form-label">Supplier:</label>
        <div class="col-md-10">
            <InputText id="supplier" class="form-control" @bind-Value="_product.Supplier" />
            <ValidationMessage For="@(() => _product.Supplier)" />
        </div>
    </div>

    <div class="form-group row">
        <label for="description" class="col-md-2 col-form-label">Description:</label>
        <div class="col-md-10">
            <InputText id="description" class="form-control" @bind-Value="_product.ProductDetails.Description" />
            <ValidationMessage For="@(() => _product.ProductDetails.Description)" />
        </div>
    </div>

    <div class="row">
        <div class="col-md-12 text-right">
            <button type="submit" class="btn btn-success">Submit</button>
        </div>
    </div>
</EditForm>

Once we are done, we can test this.

Let’s start the app.

As soon as we do that, we are going to get an error message that object reference is not set to an instance of an object. That’s because our Product model contains an additional property of the ProductDetails type and for the successful Blazor validation process, we need that instance as well. So, to solve this, we can instantiate this class in the Product.cs file or in the Index.razor.cs file. We prefer doing this in the file where the validation logic is, so, let’s modify the Index.razor.cs file:

public partial class Index
{
    private Product _product = new Product { ProductDetails = new ProductDetails() };

    private void Submit() => Console.WriteLine($"{_product.Name}, {_product.Supplier}");
}

Now, we can start our app again, and click the Submit button:

Validation of the nested object is not working

As a result, we can see that the Description field hasn’t been validated at all.

So, what is the problem?

First of all, the DataAnnotationsValidator component doesn’t support complex model validation. To support this, we have to use another component named ObjectGraphDataAnnotationsValidator. This brings us to the second point, which is that our System.ComponentModel.Annotations library doesn’t support the complex model validation.

That said, let’s install a new library in the Shared project that supports this behavior:

PM> Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4

This library fills the validation gaps for the Annotations library that we already have installed. Even though it is still in the experimental release, Microsoft suggests using it currently as is. This might change in future releases. Also, this library supports .NET Standard version 2.1 so if you have a class library project with the 2.0 version, just increase the version of the project.

After the package installation, we can replace the DataAnnotationsValidator component with the ObjectGraphDataAnnotationsValidator component:

<EditForm Model="_product" OnValidSubmit="Submit" style="width:600px;">
    <ObjectGraphDataAnnotationsValidator />
    <div class="form-group row">
        <label for="name" class="col-md-2 col-form-label">Name:</label>
        <div class="col-md-10">
            <InputText id="name" class="form-control" @bind-Value="_product.Name" />
            <ValidationMessage For="@(() => _product.Name)" />
        </div>
    </div>
...

Finally, we have to decorate the ProductDetails property in the Product class:

public class Product
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    [Required(ErrorMessage = "Supplier is required")]
    public string Supplier { get; set; }
    [ValidateComplexType]
    public ProductDetails ProductDetails { get; set; }
}

That is it. We can start our app now, and test it:

Blazor Complex Model Validation works

At this point, we can see that our validation works for all the fields.

Compare Validation in Blazor

Since we have installed a new library to support our validation, we can use another functionality it brings to the table. That’s the compare validation.

Microsoft suggests that for the Blazor form validation, we shouldn’t be using the regular [Compare] attribute to compare values of different properties. Well, let’s just quote Microsoft’s official statement: “TheĀ CompareAttributeĀ doesn’t work well with theĀ DataAnnotationsValidator component because it doesn’t associate the validation result with a specific member. This can result in inconsistent behavior between field-level validation and when the entire model is validated on a submit“.

What they suggest is using a new attribute named CompareProperty, which is a direct replacement for the Compare attribute.

So, let’s see how it works.

Let’s add a new property in the Product class:

public class Product
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    [Required(ErrorMessage = "Supplier is required")]
    public string Supplier { get; set; }
    [CompareProperty("Supplier", ErrorMessage = "Comparation failed")]
    public string ConfirmSupplier { get; set; }
    [ValidateComplexType]
    public ProductDetails ProductDetails { get; set; }
}

And, let’s modify the Index.razor file by adding another section right bellow the Supplier section:

<div class="form-group row">
    <label for="confirm" class="col-md-2 col-form-label">Confirm:</label>
    <div class="col-md-10">
        <InputText id="confirm" class="form-control" @bind-Value="_product.ConfirmSupplier" />
        <ValidationMessage For="@(() => _product.ConfirmSupplier)" />
    </div>
</div>

That’s all it takes.

If we enter different values for the Supplier and Confirm:

Compare validation in Blazor

We can see our validation error. Of course, if we make them the same, the error message will disappear:

Compare same values

Excellent.

Everything works as expected.

Conclusion

In this article, we have learned:

  • How to implement a complex model validation in Blazor
  • Which component to use to validate complex models
  • What attribute to use for the compare validation

If you combine this article with our Form Validation article and Custom Form Validation article, you are going to have quite good knowledge about form validations in Blazor applications.

Until the next article.

Best regards.