How do I enable validation without using the DataAnnotationValidator?

Platform: Blazor|Category : Forms and validation, General

Instead of using the DataAnnotationValidator in Blazor, you can use FluentValidation to implement form validation. FluentValidation is a common validation library for.NET Core that offers a few advantages over the built-in DataAnnotations validation library, such as a larger set of rules, simpler setup, and extensibility.

To use fluent validation in a Blazor application:

  1. Create a Blazor application using the link.

  2. Install the “FluentValidation” package using the NuGet package manager.

  3. Create a new folder named FluentValidation in the main application and add the following *.cs files – Employee, EmployeeValidator, FluentValidationValidator to it as shown below.
    Fluent Validation

  4. Add a model Employee class name in the Employee.cs file.

      namespace {AppName}.FluentValidation
       {
           public class Employee
           {
               public string Name { get; set; }
               public string Organization { get; set; }
           }
       }
    
  5. To write a model validator, you must create a EmployeeValidator class that is inherited from the AbstractValidator<Employee> and then add all the validation rules for the respective model to the constructor.

       using FluentValidation;
        
       namespace {AppName}.FluentValidation
       {
           public class EmployeeValidator : AbstractValidator<Employee>
           {
               public EmployeeValidator()
               {
                   RuleFor(p =>   p.Name).NotEmpty().WithMessage("You   must enter a valid name.");
                   RuleFor(p =>   p.Name).MaximumLength(20).WithMessage("The   name cannot be more than 20 characters long.");
                   RuleFor(p =>   p.Organization).NotEmpty().WithMessage("You   must enter a valid organization name.");
               }
           }
       }
    
  6. In FluentValidationValidator.cs, create a new validator component named FluentValidationValidator to replace DataAnnonationsValidator, and add the code below to it.
     
    The FluentValidationValidator receives an EditContext as a cascading parameter and hooks into the EditContext’s OnFieldChanged and OnValidationRequested events to know when something is happening in the UI. It can add or remove validation messages from a ValidationMessageStore at any time.

    using System;
       using FluentValidation;
       using Microsoft.AspNetCore.Components;
       using Microsoft.AspNetCore.Components.Forms;
        
       namespace {AppName}.FluentValidation
       {
           public class FluentValidationValidator<TValidator> : ComponentBase where TValidator :   IValidator, new()
           {
               private readonly static char[] separators = new[] { '.', '[' };
               private TValidator validator;
        
               [CascadingParameter] 
               private EditContext EditContext { get; set; }
        
               protected override void OnInitialized()
               {
                   validator = new TValidator();
                   var messages = new ValidationMessageStore(EditContext);
        
                   /* Re-validate when any field changes or when the entire form   requests validation.*/
                   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)
               {
                   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("]"))
                       {
                           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
                       {
                           var prop =   obj.GetType().GetProperty(nextToken);
                           if (prop == null)
                           {
                               throw new   InvalidOperationException($"Could not find   property named {nextToken} in object of type {obj.GetType().FullName}.");
                           }
                           newObj =   prop.GetValue(obj);
                       }
        
                       if (newObj == null)
                       {
                           return new FieldIdentifier(obj,   nextToken);
                       }
        
                       obj = newObj;
                   }
               }
           }
       }
    
  7. Add the following code to the Index.razor page to perform fluent validation using the FluentValidationValidator component and its model validator (EmployeeValidator) in the EditForm component.

      @page "/"
        
       @using {AppName}.FluentValidation;
        
       <EditForm Model="employee" OnValidSubmit="SubmitForm">
           <FluentValidationValidator TValidator="EmployeeValidator" />
           <ValidationSummary />
        
           <div class="form-group">
               <label for="name">Name:</label>
               <InputText @bind-Value="employee.Name" class="form-control" id="name" />
           </div>
        
           <div class="form-group">
               <label for="age">Organization:</label>
               <InputText @bind-Value="employee.Organization" class="form-control" />
           </div>
        
           <button type="submit" class="btn btn-primary">Submit</button>
        
       </EditForm>
        
       @code {
           Employee employee { get; set; } = new Employee();
        
           public void SubmitForm()
           {
        
           }
       }
    

    Fluent Validator Output
    View Sample in GitHub