Back to: ASP.NET Core Tutorials For Beginners and Professionals
Fluent API Validation in ASP.NET Core MVC
In this article, I will briefly introduce Fluent API Validations in ASP.NET Core MVC Applications. Please read our previous article discussing Data Annotations in ASP.NET Core MVC Applications.
What is Fluent API Validation in ASP.NET Core MVC?
When it comes to creating validation rules for models in ASP.NET Core MVC (or any ASP.NET Core Web Application), one commonly used approach is Fluent API Validation. This method involves using a fluent interface and lambda expressions and is facilitated by the library FluentValidation.
When working with ASP.NET Core, we can use Fluent API Validation to configure validation rules in a more readable and understandable manner. Instead of applying Data Annotations Attributes directly to model properties, we can define validation rules in a separate class. This approach, called “fluent,” is based on the “Fluent Interface” design pattern, which uses method chaining to create code that reads like natural language.
Why Do We Need to Use Fluent API Validation?
Fluent API validation offers several advantages over traditional validation methods in ASP.NET Core, such as Data Annotations. Here are some reasons why developers choose Fluent API validation over Data Annotation:
- Separation of Concerns: Separating validation rules from models results in cleaner code.
- Reusability and Modularity: You can apply reusable validation rules or components to different models, including DTOs, view models, or database entities.
- Advanced Validation Scenarios: Fluent API Validation enables complex validation logic, including dependent rule validation and validation of collections within objects.
- Better Readability: The Fluent API provides easier and more straightforward Validator methods to define validation rules, especially for complex validations. It uses Method Chaining which gives us the feeling of natural language, making it easier to understand the purpose of the validation.
- Conditionally Apply Rules: With FluentValidation, we can apply conditional validation rules at runtime based on property values.
- Integrate with External Systems: You can validate rules against external data sources, like databases and APIs.
- Custom Error Messages: With Fluent API Validation, customizing error messages is simple and allows for localization or parameterization based on rule configuration.
- Testability: Separating validation rules into their own classes simplifies writing unit tests and avoids mixing with the model’s other responsibilities.
- Consistent Error Response Structure: Fluent Validation, especially when used with ASP.NET Core Web APIs for developing restful services, ensures a consistent validation response structure.
- Integration with ASP.NET Core: FluentValidation seamlessly integrates with ASP.NET Core’s infrastructure, providing automatic model validation and integration with ModelState (when working with synchronous validation, not with asynchronous validation), among other features such as client-side validation.
- Avoiding Annotation Clutter: Data Annotations can clutter models with validation attributes, especially when applying multiple attributes on a single property. FluentValidation moves validation to separate classes, keeping models clean.
While Data Annotations can meet the validation needs for basic model validations, Fluent API validation may be a better choice for complex scenarios or for developers who desire more flexibility and control. It’s important to consider the specific situation when deciding which option to use.
Fluent API Validation Methods in ASP.NET Core
In ASP.NET Core, when we refer to Fluent API validation, we often talk about the library “FluentValidation”. The FluentValidation provides a rich set of methods to define validation rules fluently. Here are some of the common validation methods provided by FluentValidation:
Basic Validators:
- NotNull() / NotEmpty(): Check the property is not null or empty.
- Equal(): Checks if the property equals another value or property.
- NotEqual(): Checks the property is not equal to another value or property.
- Length(): Checks the length of a string property that falls within the given min and max range.
- Matches(): Checks the property matches a given regex pattern.
Number Validators:
- InclusiveBetween(): Checks the property value falls between or equals to the specified limits.
- ExclusiveBetween(): Checks the property value falls between but not equal to the specified limits.
- LessThan() / LessThanOrEqualTo(): For comparison with another value.
- GreaterThan() / GreaterThanOrEqualTo(): Same as above, but for greater values.
String Validators:
- EmailAddress(): Checks the property is a valid email address.
- CreditCard(): Checks the property is a valid credit card number.
- PrecisionScale: Defines a scale precision validator on the current rule builder that ensures a decimal of the specified precision and scale.
Date and Time Validators:
- InclusiveBetween(): This can also be used for DateTime ranges.
- LessThan() / .LessThanOrEqualTo(): This can also be used for DateTime values.
- GreaterThan() / .GreaterThanOrEqualTo(): Same as above, but for future DateTime values.
Conditionally Applying Rules:
- When(): Apply rules conditionally based on properties or external factors.
- Unless(): The opposite of When(), the rule is applied unless the condition is true.
Collections:
- Must(): Allows for more complex custom logic. You could, for instance, validate that a list has unique elements.
- ForEach(): Apply a rule to each element in a collection.
Custom Validators:
- Must(): Can also be used for writing custom logic for validation.
- Custom(): Allows you to specify a custom validation function.
Other Validators:
- CascadeMode: Determines how the library should continue validation once a rule has failed.
- WithMessage(): Allows setting custom error messages for validation rules.
- WithName(): Overrides the display name of a property.
- DependentRules(): Specifies that several rules should be grouped together as dependent rules.
Async Validators:
- MustAsync(): Asynchronously checks a condition.
- CustomAsync(): Asynchronously validates using custom logic.
Comparison Validators:
- Equal() / NotEqual(): This can also be used to compare properties of the same model.
How to Use Fluent API Validation in ASP.NET Core MVC?
To use Fluent API validation in ASP.NET Core MVC, the library FluentValidation is a popular choice. Here’s a step-by-step guide to integrating and using FluentValidation in an ASP.NET Core MVC Application:
Install FluentValidation Package:
Start by adding the FluentValidation.AspNetCore package to your project. You can add using NuGet Package Manager for Solution as shown below.
Using Package Manager Console: Install-Package FluentValidation.AspNetCore
Creating the Model Class:
We are using validation to validate the model class property values. So, let us first create a model class for registering a user. So, create a class file named RegistrationModel.cs and copy and paste the following code. This simple model has three properties, and we want to validate these properties.
namespace FluentAPIDemo.Models { public class RegistrationModel { public string? Username { get; set; } public string? Email { get; set; } public string? Password { get; set; } } }
Creating a Validator:
We must create a corresponding validator class for each model we want to validate. For example, for our Registration Model, we need to create a validator class and write the logic to validate the properties, as per our business requirement. So, create a class file named RegistrationValidator.cs and copy and paste the following code. We use built-in Fluent API validation methods to validate the RegistrationModel properties.
using FluentValidation; namespace FluentAPIDemo.Models { public class RegistrationValidator : AbstractValidator<RegistrationModel> { public RegistrationValidator() { RuleFor(x => x.Username) .NotEmpty().WithMessage("Username is Required.") .Length(5, 30).WithMessage("Username must be between 5 and 30 characters."); RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is Required.") .EmailAddress().WithMessage("Valid Email Address is Required."); RuleFor(x => x.Password) .NotEmpty().WithMessage("Password is Required.") .Length(6, 100).WithMessage("Password must be between 6 and 100 characters."); } } }
Understanding the above Code:
AbstractValidator<T>: This is a class provided by FluentValidation. It is the base class that we need to extend to define our validation rules for a specific model. In the example, RegistrationModel is the model being validated.
RuleFor(x => x.PropertyName): This is the starting method to define a validation rule for a specific property on the model. It sets the context for further chained methods determining how the property will be validated.
Validation methods: After RuleFor, we can chain one or more validation methods to define the rule. For example:
- NotEmpty(): The property must have a value.
- Length(min, max): The string property must have a length between min and max.
- EmailAddress(): The string property must be a valid email address.
- … and many more.
WithMessage(“…”): This method allows us to provide a custom error message for the preceding validation rule. If the validation fails, this message will be added to the ModelState errors in an ASP.NET Core MVC application.
Registering Fluent API and the Validator:
Once we have the Model class and Validator class, we need to enable Fluent API validation for our Project. To enable Fluent API Validation, we need to use the AddFluentValidationAutoValidation service, as shown below, within the Program class of our application as we are working with the .NET 6 Application. This will enable integration between FluentValidation and ASP.NET MVC’s automatic validation pipeline.
builder.Services.AddFluentValidationAutoValidation();
If you want to enable Client-Side validation, then you need to register the following AddFluentValidationClientsideAdapters method. This is not required if you work with an ASP.NET Core Web API Project. This enables integration between FluentValidation and ASP.NET client-side validation.
builder.Services.AddFluentValidationClientsideAdapters();
Then, we must register each Model class and the corresponding Validator class to the built-in dependency injection container. Here, we are using the AddTransient method to register the same as follows.
builder.Services.AddTransient<IValidator<RegistrationModel>, RegistrationValidator>();
With the above changes in place, our Program class should look as shown below:
using FluentAPIDemo.Models; using FluentValidation; using FluentValidation.AspNetCore; using System.Drawing; namespace FluentAPIDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); //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<RegistrationModel>, RegistrationValidator>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); } } }
FluentValidation will automatically validate controller action method parameters with the above setup, populating the ModelState with validation errors. You can then check ModelState.IsValid in your action methods to determine if the received input is valid.
Validating in Controllers:
Now, we need to Validate the RegistrationModel within the Controller action method. You need to remember that FluentValidation will automatically validate the model and populate the ModelState. So, modify the Home Controller class as follows:
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { public IActionResult Register() { return View(); } [HttpPost] public IActionResult Register(RegistrationModel model) { if (!ModelState.IsValid) { // Validation failed, return to the form with errors return View(model); } // Handle successful validation logic here return RedirectToAction("Success"); } public string Success() { return "Registration Successful"; } } }
Display Errors in Views:
We can display errors using the validation tag helpers in our Razor views. So, add the Register.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.RegistrationModel @{ ViewData["Title"] = "Register"; } <h1>Register User</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Register"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Username" class="control-label"></label> <input asp-for="Username" class="form-control" /> <span asp-validation-for="Username" class="text-danger"></span> </div> <div class="form-group"> <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"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div>
With these steps, we have integrated FluentValidation into our ASP.NET Core project. This approach helps create cleaner, more maintainable, and powerful validation logic for your models. Run the application, navigate to Home/Register, and check whether everything works as expected.
Here, you can observe that the Client-Side validation is not working. To ensure the client-side validation is working, we must include jQuery, jQuery Validation, and jQuery Unobtrusive Validation in your project. These can be added via CDNs or local files. For example, include them via CDNs in your _Layout.cshtml:
<!-- jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <!-- jQuery Validation Plugin --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.2/jquery.validate.min.js"></script> <!-- jQuery Unobtrusive Validation (to bridge ASP.NET Core MVC validation with jQuery Validate) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>
Please add the following files in your _Layout.cshtml to enable client-side validation using local files. The order is also important; please include them in the following order.
<script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
With the above changes, you can observe that the client-side validation is also working as expected.
Validating Manually:
Instead of using the ModelState.IsValid to check the Validation automatically, we can also check the validation manually by creating an instance of the Validator class and then invoking the Validate method. For a better understanding, please look at the following code.
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { public IActionResult Register() { return View(); } [HttpPost] public IActionResult Register(RegistrationModel model) { RegistrationValidator validator = new RegistrationValidator(); var validationResult = validator.Validate(model); if (!validationResult.IsValid) { // return BadRequest(validationResult.Errors); return View(model); } //if (!ModelState.IsValid) //{ // // Validation failed, return to the form with errors // return View(model); //} // Handle successful validation logic here return RedirectToAction("Success"); } public string Success() { return "Registration Successful"; } } }
With the above changes, run the application and see the expected behavior. Note that not all rules defined in FluentValidation will work with ASP.NET’s client-side validation. For example, rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side. The client-side support the following validators:
- NotNull/NotEmpty
- Matches (regex)
- InclusiveBetween (range)
- CreditCard
- EqualTo (cross-property equality comparison)
- MaxLength
- MinLength
- Length
By understanding these basics of Fluent API Validations, we can easily create complex validation scenarios and improve the robustness of our application’s input validation.
In the next article, I will discuss Model Validations in ASP.NET Core MVC Applications using Fluent API Validation. In this article, I try to explain Fluent API Validations in ASP.NET Core MVC with Examples. I hope you enjoy the Fluent API Validations in ASP.NET Core MVC article.
About the Author: Pranaya Rout
Pranaya Rout has published more than 3,000 articles in his 11-year career. Pranaya Rout has very good experience with Microsoft Technologies, Including C#, VB, ASP.NET MVC, ASP.NET Web API, EF, EF Core, ADO.NET, LINQ, SQL Server, MYSQL, Oracle, ASP.NET Core, Cloud Computing, Microservices, Design Patterns and still learning new technologies.