Data validation with Fluent Validation for ASP NET Core
Oscar Montenegro
Posted on April 13, 2023
Hello guys, welcome to another chapter of this series on API Development with ASP NET Core. In the last article, we talked about data mapping using auto mapper and I showed you how to implement data mapping from installing auto mapper to creating the mapping profiles and injecting auto mapper in our controller to perform the model mapping to data transfer object.
Today we will be talking about data validation using a very popular Nuget package called “Fluent Validation”. One of the coolest things about this Nuget package is that it uses a fluent interface to configure and initialize the data validators such as the fluent builder that we implemented for our Image Generator project some weeks ago, the fluent interface makes the initialization of complex objects more simple and readable and therefore it becomes more maintainable. Taking into consideration that this article is for very beginners we will touch on some basic topics on data validation.
What this article will cover? 📑
The topics that we will cover in this article are the following.
- Data validation
- What is fluent validation?
- Why se fluent validation
- Installing fluent validation Nuget package
- Create a custom validator
- Registering Fluent Validation
Data validation ✅
Data validation is an essential aspect of software development. It ensures that the data being processed is correct and consistent, leading to better performance, reliability, and security of the application. However, traditional methods of data validation can be time-consuming and error-prone. This is where Fluent Validation comes in. Fluent Validation is a library for .NET that simplifies the process of validating data in your application. In this article, we’ll explore the benefits of Fluent Validation and how it can make your life as a developer easier.
What is Fluent Validation❓
Fluent Validation is an open-source library for .NET that makes it easy to define and execute validation rules. It provides a fluent interface for defining validation rules, making it easy to read and maintain. Fluent Validation can be used to validate any object, including complex objects, lists, and arrays.
Why use Fluent Validation 🤖
- Fluent Validation provides several benefits over traditional validation methods, including:
- Increased readability and maintainability of validation code: The fluent interface provided by Fluent Validation makes it easy to read and maintain validation code. This is especially true for complex validation rules.
- Improved testability: Fluent Validation makes it easy to unit-test validation rules. This ensures that your validation rules are working as expected and that any changes you make to the rules do not break existing functionality.
- Increased productivity: By simplifying the validation process, Fluent Validation can help you write code more quickly and with fewer errors. This can save you time and reduce the likelihood of bugs.
- Better error reporting: Fluent Validation provides detailed error messages that make it easy to understand what went wrong during validation. This can help you quickly identify and fix issues.
Now that you know what data validation is and why you should consider it for complex projects like this API we can move on to put our hands on some code taking as a starting point the installation of the fluent validation nuget package into our project.
Clone the repo from GitHub
To follow along with this article make sure you the repo from GitHub and make sure you get the code from the DtosAndMapping the branch which contains the changes up to this article.
Install FluentValidation.AspNetCore NuGet package 📦
Let’s start implementing data validation and installing the NuGet package into our project using the dotnet cli or you could use the Nuget package manager.
dotnet add package FluentValidation.AspNetCore
This command will install the needed Nuget package for us to use fluent validation in our API and now we can start creating the validators for our Post model.
Add new Data Transfer Objects 📩
In the last article we discussed what are DTOs and why are they used for software development and we created one dto just for demonstration purposes. Now to create more meaningful validators we will create some more meaningful dtos as well, we will create a dto for when a user wants to create a new post when a new post is being updated and also a dto to return the posts which is one that we already have but we will use “records”.
A Record is a reference type that provides built-in functionality for encapsulating data and its properties are immutable which is just what we need for a dto.
Add new post DTO
Create a new record called AddPostDTO. When we need to create a new post we do not need to provide an Id so it becomes redundant to have the Id property as a requirement to create a post. So in this dto, we only take the title, body, and author of the post to be able to create it, and then the entity framework takes charge of passing the id to the database.
csharp
namespace BlogAPI.DTOs
{
public record AddPostDTO(string Title, string Body, string Author);
}
Edit post DTO
To update a post we do need its Id to know which post we will update.
namespace BlogAPI.DTOs
{
public record EditPostDTO(int Id, string Title, string Body, string Author);
}
Update the PostDTO to PostResponseDTO 📝
We created a PostDTO in the data mapping article to return a post without displaying the created date property and last updated date property so now we will only make this a record to be consistent with all our dtos being records and change the name to PostResponseDTO to make it more meaningful and distinguish between this dto and the EditPostDTO as they both hold the same properties.
csharp
namespace BlogAPI.DTOs
{
public record PostResponseDTO(int Id, string Author, string Title, string Body);
}
Isn’t this code duplication? ➿
You could argue that both EditPostDTO and PostResponseDTO are the same record as they hold the same properties and I did point that out a second ago but if you think it’s a good idea to use just one dto for the response and the edit as they hold the same properties right now that could be a big mistake.
Just think that if in the future our main Post model changes or our project needs changes and now I want to display the CreatedDate the property when I return a Post I would update my dto that is used to edit and return data but it makes no sense for the user to pass the CreatedDate value whenever he wants to edit the post and we would be forced to again build separate dtos for editing and to return the post. For that matter its important that we have separate dtos for that cases.
Add new mapping profiles 🗺️
As we addded new dtos and updated our old dto we have to add new mapping profiles and update our PostProfiles file.
csharp
public class PostProfiles : Profile
{
public PostProfiles()
{
CreateMap<AddPostDTO, Post>();
CreateMap<EditPostDTO, Post>();
CreateMap<Post, PostResponseDTO>();
}
}
Your PostProfiles class should look like this and also if you renamed your PostDTO to PostResponseDTO like me you will have to update the controller class as well but we will take a look on that in just a minute.
Create a custom validator ✔️
Let’s create validators for our AddPostDTO and EditPostDTO.
To define a set of validation rules for a particular object, you will need to create a class that inherits from AbstractValidator, where T is the type of class that you wish to validate.
- Fluent validation documentation
csharp
Add Post validator
public class AddPostValidator : AbstractValidator<AddPostDTO>
{
public AddPostValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(100)
.WithMessage("Title cannot exceed 100 characters");
RuleFor(x => x.Body)
.NotEmpty()
.MaximumLength(500)
.WithMessage("The body of the post cannot exceed 500 characters");
RuleFor(x => x.Author)
.NotEmpty()
.MaximumLength(100)
.WithMessage("The name of the author cannot exceed 100 characters");
}
}
First we need to create a class called AddPostValidator that inherits from the AbstractValidator interface being T our AddPostDTO dto.
Then we can see fluent validation in action creating the first rule for the title property using the RuleFor method using a lambda expression to select the title property and chaining the NotEmpty method that states that the property should not be null nor empty, afther that we call the MaximumLength method and specify the max length that our title can have and last but not least we specify an error message to be displayed if the rule is not met. Then we apply the same rules for the body and author properties as our dto is not that complex yet we do not need to specify complex rules.
Edit Post validator
csharp
public class EditPostValidator : AbstractValidator<EditPostDTO>
{
public EditPostValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id cannot be null or empty");
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(100)
.WithMessage("Title cannot exceed 100 characters");
RuleFor(x => x.Body)
.NotEmpty()
.MaximumLength(500)
.WithMessage("The body of the post cannot exceed 500 characters");
RuleFor(x => x.Author)
.NotEmpty()
.MaximumLength(100)
.WithMessage("The name of the author cannot exceed 100 characters");
}
}
For our EditPostDTO dto we will apply the same rules as for the AddPostDTO except that we will add the Id and we will specify that it cannot be empty or null and the error message to display if this rule is not met.
Register Fluent Validation in Program.cs 🧑🏾💻
Let’s register the fluent validation nuget package as a service in the Program file below AddAutomapper let’s add these two lines to proceed with updating the PostsController.
csharp
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
Update the controllers using the new DTOs and validation the models 🕺🏾
Now all that is left is to update our PostsController to add our newly added DTOs and to validate the models that the client is sending to our API so let’s begin with the CreatePost action.
csharp
[HttpPost]
public IActionResult CreatePost(AddPostDTO addPostDTO)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var newPost = mapper.Map<AddPostDTO, Post>(addPostDTO);
newPost.CreatedDate = DateTime.Now;
repository.Add(newPost);
return CreatedAtAction(nameof(GetPost), new { id = newPost.Id }, null);
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error on Post method");
throw;
}
}
you can see that we added an if statement to check whether the model state is valid and if not then we will return a bad request response to let the client know that he did a bas request and then asp net core will return the error messages that fluent validation provides by default and also the ones that we added in the validation classes. Also, we added a line to set the CreatedDate property to the moment we are creating the post.
Edit post endpoints
csharp
[HttpPut]
public IActionResult EditPost([FromBody] EditPostDTO editPostDto)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var post = mapper.Map<EditPostDTO, Post>(editPostDto);
post.LastUpdated = DateTime.Now;
repository.Edit(post);
return NoContent();
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error on Put(Edit) Method");
throw;
}
}
This is the second and last endpoint that we will update to support validation and to add our new dtos that work for the edit action. As in the last endpoint, we added an if statement to check if the model state is valid this means that there is no error in the data that was sent to the API and then we map the EditPostDTO to the former Post model to edit it and if you pay attention we added a line to update the LastUpdated property so that every time we update a post this property is updated automatically to that corresponding post.
Test your API 🧪
If you test the API and pass some invalid data then the response should look something like this.
json
{
"type": "<https://tools.ietf.org/html/rfc7231#section-6.5.1>",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-df250c99457dff70b0defd6d812b941f-7313f895506c9c22-00",
"errors": {
"Title": [
"Title cannot exceed 100 characters"
],
"Author": [
"The name of the author cannot exceed 100 characters"
]
}
}
We are getting a 400 BadRequest response message and an array called errors with the rules we are breaking being these, the title rule and author rule as we are exceeding the limit of characters that we set on our validator class. Pretty satisfying don’t you think? Now you will avoid your API being full of invalid data and incomplete data so now we made it more resilient.
Conclusion 🌇
Data validation is essential for software development just imagine if anybody could insert any data into your API, that could be a mess. With data validation, we are making sure that only consistent and meaningful data is going into our database. Fluent validation is an excellent library that simplifies the process of data validation in our applications.
Thanks for your support, it means the world to me! 🌍🙏🏾
I always take a space at the end of every post to ask you to support me and follow me on my blog and youtube channel but today I want to thank you all for supporting me because the reason that I keep writing is that there are people like you that take some time and dedicate a special space to read whatever I’m writing and that means a lot to me it keeps me motivated and helps me to think that somebody in the world in the other side of this bright and clear screen is reading it and it’s being of use for him/she.
Thanks, everybody I will continue this API Development series until I feel that every basic aspect is covered and only then I will move on to another series. Also, tomorrow is Friday and you know what that means, yay API Friday!!! I have been working on something special for you guys, nothing complex just something that you can build to have a good time by the way if you want to know what was the last project for API Friday it was an image generator using the lorem picsum API and Blazor so check it out, I know you will have lots of fun coding it.
So that would be it for today I have to prepare everything for tomorrow so again thanks for your support and see you in my next post!
Posted on April 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.