👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
Blazor WASM Forms Validation

Blazor WASM Forms Validation

Author - Abdul Rahman (Bhai)

Blazor

34 Articles

Improve

Table of Contents

  1. What we gonna do?
  2. Why we gonna do?
  3. How we gonna do?
  4. Summary

What we gonna do?

In this article, let's learn about how to validate the data collected using forms in Blazor WASM application.

Note: If you have not done so already, I recommend you read the article on Blazor Wasm Forms.

Why we gonna do?

Validation is the act of checking the validity of data. It is a very important part of the form validation process. We should always validate the user data before submitting it to the server. Validation can be from simple data format validation to complex business validations. Simple data format validations like checking email format can be done in client side while checking email uniquness in system can be done in server side. Processing the raw data can sometime allow an attacker to exploit our systems. Hence it is always recommended to validate the data before processing.

How we gonna do?

Introducing DataAnnotationsValidator

The DataAnnotationsValidator is the standard validator type in Blazor. Adding this component within an EditForm component will enable form validation based on .NET Data Annotation attributes

@using System.ComponentModel.DataAnnotations

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />
    
    <ValidationMessage For=@(() => exampleModel.Name)/>

    <button type="submit">Submit</button>

    <p>
        @message
    </p>
</EditForm>

@code {
    public class ExampleModel
    {
        [Required]
        [StringLength(10, ErrorMessage = "Name is too long.")]
        public string? Name { get; set; }
    }

    private ExampleModel exampleModel = new();
    private string? message;

    private void HandleValidSubmit()
    {
        message = "HandleValidSubmit called";

        // Process the valid form
    }
}
        
Demo Space

Displaying Error Messages

Validation error messages can be displayed to the user in two ways. We can add a ValidationSummary to show a comprehensive list of all errors in the form. We can also use the ValidationMessage component to display error messages for a specific input on the form. These components are not mutually exclusive, so it is possible to use both at the same time. The ValidationSummary component can simply be dropped into an EditForm in our mark-up; no additional parameters are required at all.

As the ValidationMessage component displays error messages for a single field, it requires us to specify the identity of the field.

In the above demo when you submit the form with invalid value, you can see two error messages getting displayed. The error message above text box is from ValidationSummary and the error message below the text box is from ValidationMessage.

Fluent Validations

FluentValidation is a popular validation library for .NET by Jeremy Skinner. It has some advantages over .NET's built-in DataAnnotations validation system, such as a richer set of rules, easier configuration, and easier extensibility.

FluentValidation does not provide integration with Blazor out of the box. Let's learn how to make Fluent Validation integrate and work nicely with Blazor. Lets start by using the same ExampleModel and configure and validate using Fluent Validation using below steps.

  1. Install Nuget Package - FluentValidation
  2. Add the below FluentValidator<TValidator> to your project. This code snippet updated version of Fluent Validatoe originally created by Steve Sanderson. I have added original source link in the snippet.
  3. Now Create a YourModelClassValidator : AbstractValidator<YourModelClass>
  4. Finally add <FluentValidator TValidator="YourModelClassValidator" />

//https://gist.github.com/SteveSandersonMS/090145d7511c5190f62a409752c60d00
public class FluentValidator<TValidator> : ComponentBase where TValidator : IValidator, new()
{
    private static readonly char[] _separators = new[] { '.', '[' };
    private TValidator _validator;

    [CascadingParameter] private EditContext EditContext { get; set; }

    protected override void OnInitialized()
    {
        _validator = new TValidator();
        var messages = new ValidationMessageStore(EditContext);

        // Revalidate when any field changes, or if the entire form requests validation
        // (e.g., on submit)

        EditContext.OnFieldChanged += (sender, eventArgs)
            => ValidateModel((EditContext)sender, messages);

        EditContext.OnValidationRequested += (sender, eventArgs)
            => ValidateModel((EditContext)sender, messages);
    }

    private void ValidateModel(EditContext editContext, ValidationMessageStore messages)
    {
        var context = new ValidationContext<object>(editContext.Model);
        var validationResult = _validator.Validate(context);
        messages.Clear();
        foreach (var error in validationResult.Errors)
        {
            var fieldIdentifier = ToFieldIdentifier(editContext, error.PropertyName);
            messages.Add(fieldIdentifier, error.ErrorMessage);
        }
        editContext.NotifyValidationStateChanged();
    }

    private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string propertyPath)
    {
        // This method parses property paths like 'SomeProp.MyCollection[123].ChildProp'
        // and returns a FieldIdentifier which is an (instance, propName) pair. For example,
        // it would return the pair (SomeProp.MyCollection[123], "ChildProp"). It traverses
        // as far into the propertyPath as it can go until it finds any null instance.

        var obj = editContext.Model;

        while (true)
        {
            var nextTokenEnd = propertyPath.IndexOfAny(_separators);
            if (nextTokenEnd < 0)
            {
                return new FieldIdentifier(obj, propertyPath);
            }

            var nextToken = propertyPath.Substring(0, nextTokenEnd);
            propertyPath = propertyPath.Substring(nextTokenEnd + 1);

            object newObj;
            if (nextToken.EndsWith("]"))
            {
                // It's an indexer
                // This code assumes C# conventions (one indexer named Item with one param)
                nextToken = nextToken.Substring(0, nextToken.Length - 1);
                var prop = obj.GetType().GetProperty("Item");
                var indexerType = prop.GetIndexParameters()[0].ParameterType;
                var indexerValue = Convert.ChangeType(nextToken, indexerType);
                newObj = prop.GetValue(obj, new object[] { indexerValue });
            }
            else
            {
                // It's a regular property
                var prop = obj.GetType().GetProperty(nextToken);
                if (prop == null)
                {
                    throw new InvalidOperationException($"Could not find property named {nextToken} on object of type {obj.GetType().FullName}.");
                }
                newObj = prop.GetValue(obj);
            }

            if (newObj == null)
            {
                // This is as far as we can go
                return new FieldIdentifier(obj, nextToken);
            }

            obj = newObj;
        }
    }
}
        
@using System.ComponentModel.DataAnnotations
@using FluentValidation

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <FluentValidator TValidator="FluentExampleModelValidator"/>
    <ValidationSummary />

    <InputText @bind-Value="exampleModel.Name" />
               
    <ValidationMessage For=@(() => exampleModel.Name)/>

    <button type="submit">Submit</button>

    <p>
        @message
    </p>
</EditForm>

@code {
    public class FluentExampleModel
    {
        public string Name { get; set; } = string.Empty;
    }

    public class FluentExampleModelValidator : AbstractValidator<FluentExampleModel>
    {
        public FluentExampleModelValidator()
        {
            RuleFor(x => x.Name)
            .NotEmpty()
            .WithMessage("Name is required.")
            .MaximumLength(10)
            .WithMessage("Name is too long.");
        }
    }

    private FluentExampleModel exampleModel = new();
    private string? message;

    private void HandleValidSubmit()
    {
        message = "HandleValidSubmit called";

        // Process the valid form
    }
}
        
Demo Space

Note that I use the above method in my production apps and it works well as of now. But there are several third party libraries you can use to do this as mentioned in Fluent Validation official site:

Summary

In this article, we learned how to validate data collected using Forms in blazor application using various techniques like DataAnnotationsValidator using .NET's built in Data Annotation Validation Attributes and FluentValidator using FluentValidation Library and how to display the error messages and feedback to user. DataAnnotations Validator is suffice for simple scenarios but for complex scenarios, I'd recommend FluentValidator.

👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.
  • Blazor
  • Forms
  • Validation
  • Fluent Validation