Back to: ASP.NET Core Tutorials For Beginners and Professionals
Real-Time Examples of Fluent API Validations in ASP.NET Core MVC
In this article, I will discuss Different Real-Time Examples of Fluent API Validations in ASP.NET Core MVC Applications. Please read our previous article discussing Fluent API Custom Validator in ASP.NET Core MVC Applications. FluentValidation is particularly powerful when handling complex validation scenarios. Here, we will explore some complex validation cases and how they can be implemented using FluentValidation in ASP.NET Core MVC.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC: Order Processing
Let us understand How we can validate the order processing in an e-commerce application using Fluent API Validation.
Define the Model:
Let’s consider the following Order model.
namespace FluentAPIDemo.Models { public class Order { public string OrderId { get; set; } public DateTime OrderDate { get; set; } public decimal Amount { get; set; } public string? CouponCode { get; set; } public List<OrderItem>? OrderItems { get; set; } } public class OrderItem { public string? ProductName { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } } }
Set Up the Validator:
Following are the Validation Rules that we need to check while processing the order:
- Ensure OrderId matches a certain pattern.
- Ensure that when a CouponCode is provided, the Amount exceeds a certain value.
- Validate a collection, ensuring each OrderItem has valid data and additional custom rules.
Implementing Complex Validators:
using FluentValidation; namespace FluentAPIDemo.Models { public class OrderValidator : AbstractValidator<Order> { public OrderValidator() { // Ensure OrderId matches a pattern (e.g., ORD-XXXX where XXXXXX is numbers) RuleFor(o => o.OrderId) .Matches("^ORD-\\d{6}$") .WithMessage("OrderId must follow the pattern ORD-XXXXXX."); // Ensure that when a CouponCode is provided, the Amount exceeds a certain value (e.g., $100) When(o => !string.IsNullOrEmpty(o.CouponCode), () => { RuleFor(o => o.Amount) .GreaterThan(100) .WithMessage("Amount must exceed $100 to use a coupon."); }); // Validate each OrderItem in the OrderItems collection RuleForEach(o => o.OrderItems) .SetValidator(new OrderItemValidator()); } } public class OrderItemValidator : AbstractValidator<OrderItem> { public OrderItemValidator() { RuleFor(i => i.ProductName) .NotEmpty() .WithMessage("ProductName cannot be empty."); RuleFor(i => i.Quantity) .GreaterThan(0) .WithMessage("Quantity must be greater than 0."); // Ensure that if UnitPrice is 0, Quantity is not more than 3 (e.g., max 3 free items per order) RuleFor(i => i.UnitPrice) .GreaterThan(0) .When(i => i.Quantity > 3) .WithMessage("Maximum of 3 free items are allowed."); } } }
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the Model and corresponding validator.
//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 the client side builder.Services.AddTransient<IValidator<Order>, OrderValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { public IActionResult CreateOrder() { List<OrderItem> products = new List<OrderItem>() { new OrderItem(){ProductName = "Product1", Quantity=1, UnitPrice=10}, new OrderItem(){ProductName = "Product2", Quantity=2, UnitPrice=15}, new OrderItem(){ProductName = "Product2", Quantity=2, UnitPrice=0}, new OrderItem(){ProductName = "Product3", Quantity=1, UnitPrice=25}, }; decimal TotalAmount = 0; foreach (var item in products) { TotalAmount = TotalAmount + ((decimal)(item.Quantity * item.UnitPrice)); } Order order = new Order() { OrderDate = DateTime.Now, OrderId = "ORD-123456", Amount = TotalAmount, OrderItems = products }; return View(order); } [HttpPost] public IActionResult CreateOrder(Order order) { if (!ModelState.IsValid) { return View(order); // Return with validation errors. } // Process the order... return RedirectToAction("Success"); } public string Success() { return "Registration Successful"; } } }
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add CreateOrder.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.Order @{ ViewData["Title"] = "CreateOrder"; } <h1>CreateOrder</h1> <div class="row"> <div class="col-md-4"> <form asp-action="CreateOrder" asp-controller="Home" method="post"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="OrderId" class="control-label"></label> <input asp-for="OrderId" class="form-control" /> <span asp-validation-for="OrderId" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="OrderDate" class="control-label"></label> <input asp-for="OrderDate" type="date" class="form-control" /> <span asp-validation-for="OrderDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Amount" class="control-label"></label> <input asp-for="Amount" class="form-control" /> <span asp-validation-for="Amount" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="CouponCode" class="control-label"></label> <input asp-for="CouponCode" class="form-control" /> <span asp-validation-for="CouponCode" class="text-danger"></span> </div> @for (int i = 0; i < Model?.OrderItems?.Count; i++) { <div class="form-group"> <!-- Product Name --> @Html.HiddenFor(m => m.OrderItems[i].ProductName) <label asp-for="@Model.OrderItems[i].ProductName">Product Name:</label> <span> <b>@Model.OrderItems[i].ProductName</b> </span> <!-- Quantity --> @Html.HiddenFor(m => m.OrderItems[i].Quantity) <label asp-for="@Model.OrderItems[i].Quantity">Quantity:</label> <span> <b>@Model.OrderItems[i].Quantity</b> </span> <!-- Quantity --> @Html.HiddenFor(m => m.OrderItems[i].UnitPrice) <label asp-for="@Model.OrderItems[i].UnitPrice">UnitPrice:</label> <span><b>@Model.OrderItems[i].UnitPrice</b></span> </div> <br /> } <input type="submit" value="Place Order" class="btn btn-primary" /> </form> </div> </div>
Real-Time Example of Fluent API Validations in ASP.NET Core MVC: Website for a Library
Let’s go through a real-world example. Imagine you’re building a website for a library, and you need to validate data for adding a new book.
Define the Model:
We’ll start with a Book model:
namespace FluentAPIDemo.Models { public class Book { public string ISBN { get; set; } public string Title { get; set; } public string Author { get; set; } public DateTime PublishDate { get; set; } public decimal Price { get; set; } } }
Set Up the Validator:
Now, we’ll set up validation:
- The ISBN should match a certain pattern.
- The title should be mandatory and have a maximum length.
- The author should also be mandatory.
- The publish date shouldn’t be in the future.
- The price should be positive.
Here’s the validator:
using FluentValidation; namespace FluentAPIDemo.Models { public class BookValidator : AbstractValidator<Book> { public BookValidator() { RuleFor(b => b.ISBN) .NotEmpty().WithMessage("ISBN is required.") .Matches("^(97(8|9))?\\d{9}(\\d|X)$").WithMessage("Invalid ISBN format."); RuleFor(b => b.Title) .NotEmpty().WithMessage("Title is required.") .MaximumLength(200).WithMessage("Title cannot exceed 200 characters."); RuleFor(b => b.Author) .NotEmpty().WithMessage("Author is required."); RuleFor(b => b.PublishDate) .NotEmpty().WithMessage("Publish date is required.") .LessThan(DateTime.Now).WithMessage("Publish date cannot be in the future."); RuleFor(b => b.Price) .GreaterThan(0).WithMessage("Price should be positive."); } } }
Understanding Matches(“^(97(8|9))?\\d{9}(\\d|X)$”):
The regular expression ^(97(8|9))?\d{9}(\d|X)$ is a pattern for validating ISBN-10 and ISBN-13 numbers. Let’s break it down:
^: Matches the start of a string.
(97(8|9))?: This matches the prefixes ‘978’ or ‘979’ common for ISBN-13. The trailing “?” means this part is optional, so it can also validate ISBN-10.
- (97: Matches the string ’97’.
- (8|9): Matches an ‘8’ or a ‘9’.
- ): Closes the group.
- ?: Specifies that the preceding element (in this case, the whole 97(8|9) group) is optional.
\d{9}: Matches 9 digits. \d is a shorthand character class that matches any digit from 0 to 9.
(\d|X): Matches either a digit or the letter ‘X’. The ‘X’ is used in ISBN-10 numbers as a checksum character.
$: Matches the end of a string.
So, the regex can match ISBN-13 numbers that start with ‘978’ or ‘979’, followed by 9 digits, and ending with either a digit or ‘X’. It can also match ISBN-10 numbers, 9 digits followed by a digit or ‘X’. Examples of valid matches would be:
- 9781234567890 (an example of ISBN-13)
- 123456789X (an example of ISBN-10 where ‘X’ is the checksum character)
This regex is versatile in matching both formats of the ISBN numbers, making it useful for validation purposes.
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the Model and corresponding validator.
//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 the client side builder.Services.AddTransient<IValidator<Book>, BookValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { public IActionResult AddBook() { return View(); } [HttpPost] public IActionResult AddBook(Book book) { if (!ModelState.IsValid) { return View(book); // Return with validation errors. } // Save the book to database or process further... return RedirectToAction("Success"); } public string Success() { return "Book Added Successful"; } } }
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add AddBook.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.Book @{ ViewData["Title"] = "AddBook"; } <h1>AddBook</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="AddBook" asp-controller="Home" method="post"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Title" class="control-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Author" class="control-label"></label> <input asp-for="Author" class="form-control" /> <span asp-validation-for="Author" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ISBN" class="control-label"></label> <input asp-for="ISBN" class="form-control" /> <span asp-validation-for="ISBN" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="PublishDate" class="control-label"></label> <input asp-for="PublishDate" type="date" class="form-control" /> <span asp-validation-for="PublishDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Price" class="control-label"></label> <input asp-for="Price" type="number" class="form-control" /> <span asp-validation-for="Price" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Add Book" class="btn btn-primary" /> </div> </form> </div> </div>
This application for adding a new book to a library system shows how to use FluentValidation to handle real-world validation scenarios in ASP.NET Core MVC, providing a clean and organized way to enforce data integrity.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC: Workshop Registration
Let’s understand another real-world example using FluentValidation in ASP.NET Core MVC. In this example, we’ll consider an online registration system for a training workshop.
Define the Model:
We will use the following WorkshopRegistration model:
namespace FluentAPIDemo.Models { public class WorkshopRegistration { public string FullName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public DateTime DateOfBirth { get; set; } public string WorkshopTopic { get; set; } } }
Set Up the Validator:
For our WorkshopRegistration model, we want the following Validations.
- FullName should be mandatory and have a specific length range.
- Email should be a valid email format.
- PhoneNumber should match a pattern.
- DateOfBirth should ensure the registrant is above a certain age.
- WorkshopTopic should be selected from a predefined list.
The following is our validator:
using FluentValidation; namespace FluentAPIDemo.Models { public class WorkshopRegistrationValidator : AbstractValidator<WorkshopRegistration> { public WorkshopRegistrationValidator() { RuleFor(r => r.FullName) .NotEmpty().WithMessage("Full Name is required.") .Length(5, 50).WithMessage("Full Name should be between 5 and 50 characters."); RuleFor(r => r.Email) .NotEmpty().WithMessage("Email is required.") .EmailAddress().WithMessage("Enter a valid email address."); RuleFor(r => r.PhoneNumber) .NotEmpty().WithMessage("Phone Number is required.") .Matches(@"^[0-9]{10}$").WithMessage("Enter a valid 10-digit phone number."); RuleFor(r => r.DateOfBirth) .NotEmpty().WithMessage("Date of Birth is required.") .Must(BeAValidAge).WithMessage("Registrant must be at least 18 years old."); RuleFor(r => r.WorkshopTopic) .Must(topic => new List<string> { "Web Development", "Data Science", "AI", "Design" }.Contains(topic)) .WithMessage("Invalid workshop topic selected."); } private bool BeAValidAge(DateTime date) { int age = DateTime.Today.Year - date.Year; if (date > DateTime.Today.AddYears(-age)) { age--; } return age >= 18; } } }
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the Model and corresponding validator.
//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<WorkshopRegistration>, WorkshopRegistrationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller 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(WorkshopRegistration registration) { if (!ModelState.IsValid) { return View(registration); // Return with validation errors. } // Process the registration... return RedirectToAction("Success"); } public string Success() { return "Registration Successful"; } } }
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add Register.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.WorkshopRegistration @{ 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"> <label asp-for="FullName" class="control-label"></label> <input asp-for="FullName" class="form-control" /> <span asp-validation-for="FullName" 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="PhoneNumber" class="control-label"></label> <input asp-for="PhoneNumber" class="form-control" /> <span asp-validation-for="PhoneNumber" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="DateOfBirth" class="control-label"></label> <input asp-for="DateOfBirth" type="date" class="form-control" /> <span asp-validation-for="DateOfBirth" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="WorkshopTopic" class="control-label"></label> <select asp-for="WorkshopTopic"> <option value="Web Development">Web Development</option> <option value="Data Science">Data Science</option> <option value="AI">AI</option> <option value="Design">Design</option> <option value="Invalid">Invalid</option> </select> <span asp-validation-for="WorkshopTopic"></span> </div> <div class="form-group"> <input type="submit" value="Register" class="btn btn-primary" /> </div> </form> </div> </div>
This example demonstrates how FluentValidation can handle various validation needs for an online workshop registration system in an ASP.NET Core MVC application. It provides a systematic and organized approach, ensuring a robust registration process.
Real-Time Example of Fluent API Validations in ASP.NET Core MVC: Hotel Reservation
Let us understand another real-world example using FluentValidation in ASP.NET Core MVC. Now, we will consider a scenario where a user can reserve a room at a hotel.
Define the Model:
Here’s our HotelReservation model:
namespace FluentAPIDemo.Models { public class HotelReservation { public string GuestName { get; set; } public string Email { get; set; } public DateTime CheckInDate { get; set; } public int NumberOfNights { get; set; } public string RoomType { get; set; } } }
Set Up the Validator:
For our HotelReservation model:
- GuestName should be mandatory and have a specific length.
- Email should be a valid email format.
- CheckInDate shouldn’t be in the past.
- NumberOfNights should be positive and below a certain limit (e.g., a maximum of 30 nights).
- RoomType should be selected from a predefined list.
Here’s the validator:
using FluentValidation; namespace FluentAPIDemo.Models { public class HotelReservationValidator : AbstractValidator<HotelReservation> { public HotelReservationValidator() { RuleFor(r => r.GuestName) .NotEmpty().WithMessage("Guest Name is required.") .Length(2, 100).WithMessage("Guest Name should be between 2 and 100 characters."); RuleFor(r => r.Email) .NotEmpty().WithMessage("Email is required.") .EmailAddress().WithMessage("Enter a valid email address."); RuleFor(r => r.CheckInDate) .GreaterThan(DateTime.Today).WithMessage("Check-In date cannot be in the past.") .LessThan(DateTime.Today.AddYears(1)).WithMessage("Reservations can only be made up to one year in advance."); RuleFor(r => r.NumberOfNights) .GreaterThan(0).WithMessage("You must reserve at least one night.") .LessThanOrEqualTo(30).WithMessage("You can reserve a maximum of 30 nights."); RuleFor(r => r.RoomType) .Must(type => new List<string> { "Single", "Double", "Suite" }.Contains(type)) .WithMessage("Invalid room type selected."); } } }
Registering the Validator:
Add the following code with the Program class to register Fluent API Validation and the Model and corresponding validator.
//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 the client side builder.Services.AddTransient<IValidator<HotelReservation>, HotelReservationValidator>();
Using the Validator in ASP.NET Core MVC Actions:
Modify the Home Controller as follows:
using FluentAPIDemo.Models; using Microsoft.AspNetCore.Mvc; namespace FluentAPIDemo.Controllers { public class HomeController : Controller { public IActionResult BookRoom() { return View(); } [HttpPost] public IActionResult BookRoom(HotelReservation reservation) { if (!ModelState.IsValid) { return View(reservation); // Return with validation errors. } // Process the reservation... return RedirectToAction("Success"); } public string Success() { return "Booking Successful"; } } }
Displaying Errors in Views:
You can use the tag helpers to bind and display errors. Add BookRoom.cshtml view and copy and paste the following code.
@model FluentAPIDemo.Models.HotelReservation @{ ViewData["Title"] = "BookRoom"; } <h1>BookRoom</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="BookRoom" asp-controller="Home" method="post"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="GuestName" class="control-label"></label> <input asp-for="GuestName" class="form-control" /> <span asp-validation-for="GuestName" 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="CheckInDate" class="control-label"></label> <input asp-for="CheckInDate" type="date" class="form-control" /> <span asp-validation-for="CheckInDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="NumberOfNights" class="control-label"></label> <input asp-for="NumberOfNights" type="number" class="form-control" /> <span asp-validation-for="NumberOfNights" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="RoomType" class="control-label"></label> <select asp-for="RoomType"> <option value="Single">Single</option> <option value="Double">Double</option> <option value="Suite">Suite</option> </select> <span asp-validation-for="RoomType"></span> </div> <div class="form-group"> <input type="submit" value="Book Room" class="btn btn-primary" /> </div> </form> </div> </div>
This example demonstrates how Fluent API Validation can effectively handle different validation scenarios for a hotel room booking system in an ASP.NET Core MVC application. It provides a structured and organized approach, ensuring a user-friendly booking experience.
In the next article, I will discuss the Differences Between Data Annotations and Fluent API in ASP.NET Core MVC. In this article, I try to explain Real-Time Examples of Fluent API Validations in ASP.NET Core MVC. I hope you enjoy these Real-Time Examples of Fluent API Validations in the ASP.NET Core MVC article.