Back to: ASP.NET Core Tutorials For Beginners and Professionals
Fluent API Async Validators in ASP.NET Core MVC:
In this article, I will discuss Fluent API Async Validator in ASP.NET Core MVC Applications with Examples. Please read our previous article discussing the Different Types of Fluent API Validation Examples in ASP.NET Core MVC Applications.
Fluent API Async Validators in ASP.NET Core MVC:
Fluent API Async Validators in ASP.NET Core MVC allows us to perform complex validation logic that integrates with the model binding and validation processes asynchronously, which can be useful for operations that require I/O operations like database lookups, API calls, or other time-consuming processes that should not block the main execution flow. The following two Validator Methods are provided by Fluent API to perform the validation asynchronously.
- MustAsync(): MustAsync() is used when applying a custom asynchronous predicate function to a property. This method is particularly useful when the validation logic involves asynchronous operations such as database checks, API calls, or other IO-bound operations.
- CustomAsync(): Use CustomAsync() when you need more control over the validation process than what MustAsync() offers. This method allows you to perform any asynchronous operation using custom logic.
Let’s understand how to use these Fluent API Asynchronous Validators in an ASP.NET Core MVC application.
Define the Model:
Let us consider a simple UserRegistration model where we want to ensure that the provided email address isn’t already registered. So, create a class file named UserRegistration.cs and copy and paste the following code.
namespace FluentAPIDemo.Models { public class UserRegistration { public string? Email { get; set; } public string? Password { get; set; } } }
Service
We need to create a service to Check Whether the Email ID Exists in the Database. So, create a class file named UserService.cs and copy and paste the following code. As you can see in the following code, the EmailExistsAsync method is used to check if an email is registered.
namespace FluentAPIDemo.Models { public class UserService { public Task<bool> EmailExistsAsync(string email) { //In Real-Time, you need to check the database //here, we are hard coding the Email Ids List<string> emails = new List<string>() { "user1@example.com", "user2@example.com", "user2@example.com", "user4@example.com" }; if (emails.Contains(email)) { return Task.FromResult(true); } else { return Task.FromResult(false); } } } }
Registering the UserService
Please add the following code to the Program.cs class file.
builder.Services.AddTransient<UserService>();
Set Up the Validator:
Now, let’s define a validator for the UserRegistration model. So, create a class file named UserRegistrationValidator.cs and copy and paste the following code. As you can see in the following code, we have used the MustAsync and CustomAsync validator methods to write the custom logic, invoking the EmailExistsAsync method to validate whether the Email is already registered.
using FluentValidation; namespace FluentAPIDemo.Models { public class UserRegistrationValidator : AbstractValidator<UserRegistration> { public UserRegistrationValidator(UserService userService) { // Synchronous validation RuleFor(x => x.Password) .NotEmpty() .WithMessage("Password is required."); // Asynchronous validation // MustAsync //RuleFor(x => x.Email) // .NotEmpty() // .EmailAddress() // .MustAsync(async (email, cancellation) => // !await userService.EmailExistsAsync(email)) // .WithMessage("Email is already registered."); // Asynchronous validation // CustomAsync RuleFor(p => p.Email) .CustomAsync(async (email, context, cancellation) => { bool isUnique = await userService.EmailExistsAsync(email); if (isUnique) { context.AddFailure($"Email: {email} is already registered"); } }); } } }
Explanation:
- MustAsync: This method is also used for asynchronous operations. The provided predicate should return true if validation succeeds and false if it fails. In this case, it invokes the userService.EmailExistsAsync(email) asynchronous method (this method must be asynchronous) and should return true or false.
- CustomAsync: The CustomAsync Validator method provides greater flexibility than MustAsync, allowing for custom failure messages based on complex asynchronous logic. In this case, it is also calling the userService.EmailExistsAsync(email) async method, and then, based on the outcome, it also sets a custom error message, which is impossible using the MustAsync method.
Registering the Validator:
Add the following code to the Program class to register Fluent API Validation, the Model, and the corresponding model validator. You must disable auto-validation when working with the async validator; otherwise, you will get a run-time exception.
//Enables integration between FluentValidation and ASP.NET MVC's automatic validation pipeline. //builder.Services.AddFluentValidationAutoValidation(); //Enables integration between FluentValidation and ASP.NET client-side validation. builder.Services.AddFluentValidationClientsideAdapters(); //Registering Model and Validator to show the error message on client side builder.Services.AddTransient<IValidator<UserRegistration>, UserRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows. Since we’re using asynchronous validation, ensure that the ASP.NET Core MVC Controller action method is also asynchronous. With the async validator in place, Auto Validation will not work, so we need to create an instance of UserRegistrationValidator and invoke the ValidateAsync method by passing the UserRegistration object as a parameter that needs to be validated.
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { private readonly UserService _userService; public HomeController(UserService userService) { _userService = userService; } public IActionResult Register() { return View(); } [HttpPost] public async Task<IActionResult> Register(UserRegistration registration) { var validationResult = await new UserRegistrationValidator(_userService).ValidateAsync(registration); if (!validationResult.IsValid) { foreach (var error in validationResult.Errors) { ModelState.AddModelError(error.PropertyName, error.ErrorMessage); } return View(registration); // Return with validation errors. } // Redirect to the Success Page return RedirectToAction("Success"); } public string Success() { return "Registration Successful"; } } }
Displaying Errors in Views:
Now, we need to display the validator error inside a view. We can use the tag helpers to bind and display model validation errors here. So, add Register.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.UserRegistration @{ ViewData["Title"] = "Register"; } <h1>User Register</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Register" asp-controller="Home" method="post"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group mt-3"> <label asp-for="Email" class="control-label"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group mt-3"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" type="password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group mt-3"> <input type="submit" value="Register" class="btn btn-primary" /> </div> </form> </div> </div> @section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }
Now, run the application, and you should see the Validation Messages as expected, as shown in the below image:
The most important point is to use the MustAsync and CustomAsync Fluent API Validators only when necessary. Performing too many I/O-bound operations during validation can slow down your application. Secondly, with MustAsync and CustomAsync Fluent API Validators, ASP.NET Core MVC Auto Validation will not work, and in that case, we need to do the validation manually by calling the ValidateAsync method.
Differences Between Fluent API Sync vs. Async Validators in ASP.NET Core MVC
Fluent API provides options for both synchronous and asynchronous validation. Here are the key differences between using Fluent API’s synchronous and asynchronous validators in ASP.NET Core:
Fluent API Synchronous Validators
Synchronous validators are the traditional type of validators that execute on the server in a blocking manner as part of the request processing pipeline. This means the thread handling the request will wait until the validation is complete before continuing.
- Execution: Runs on the current thread and blocks it until the validation is complete.
- Usage Scenario: This is ideal for quick validations that do not involve I/O operations, like checking string lengths, numeric ranges, or complex rules that don’t require database or network calls.
- API Methods: Typically, methods like Validate() are used.
Fluent API Asynchronous Validators
Asynchronous validators, on the other hand, allow the validation process to be non-blocking. They are designed to perform I/O-bound tasks, such as database lookups or calls to external services, without blocking the executing thread.
- Execution: Runs without blocking the thread. It allows the thread to be used for other tasks while waiting for the validation logic that involves I/O operations to complete.
- Usage Scenario: Best suited for validations that involve external data sources, like database constraint checks, API calls, or any operations that can be delayed without impacting the thread’s ability to serve other requests.
- API Methods: Methods like ValidateAsync() are used.
Choosing Between Sync and Async Validators
The choice between synchronous and asynchronous validators typically depends on the nature of the validation task:
- Performance Considerations: Synchronous validators are generally sufficient and simpler to implement for non-I/O-bound validations. Asynchronous validators should be considered when the validation involves I/O operations, which could potentially slow down the request processing if done synchronously.
- Resource Utilization: Asynchronous validators help better utilize server resources, especially under load, as they free up the server thread to handle other requests while waiting for I/O operations to complete.
In the next article, I will discuss Fluent API Custom Validators in ASP.NET Core MVC Applications with Examples. In this article, I try to explain Fluent API Async Validators in ASP.NET Core MVC Applications with Examples. I hope you enjoy this article.