When it comes to Validating Models and incoming HTTP Requests, aren’t we all leaning toward Data Annotations? Although it can be quick to set up Data Annotations on your models, there are a few drawbacks to this approach.
In this article, we will try to understand the problems with Data Annotations for larger projects and use an alternative, FluentValidation in our ASP.NET Core application. It can turn up the validation game to a whole new level, giving you total control and writing much cleaner code.
The Need for Validation
Data validation is a critical aspect of developing robust and secure web applications. It ensures that the data entered by users is accurate, reliable, and safe for processing. Without proper validation, applications are vulnerable to various issues, including:
- Data Integrity: Validation ensures that the data conforms to the expected format and rules, preventing incorrect or incomplete data from being saved in the database. This helps maintain the integrity of the data and ensures its consistency.
- Security: Proper validation helps prevent security vulnerabilities such as SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF) by ensuring that user input is sanitized and safe for processing.
- User Experience: Validation improves the user experience by providing immediate feedback to users about the correctness of their input. This helps users quickly correct any mistakes, reducing frustration and improving usability.
- Business Logic: Validation enforces business rules and logic, ensuring that data meets the requirements of the application.
Why are Data Annotations not suitable always?
The GO-TO Approach for Model validation in any .NET Application is Data Annotations, where you have to declare attributes over the properties of respective classes. Worked with it before?
In the above example, we have a UserRegistraionRequest
with fields like Email, Name, and Password. To ensure that the client has given us valid input, we introduced Data Annotations
to validate the request. For instance, the FirstName field is a required property, the ComparePassword field should match the Password field, and so on.
It is fine for small projects and POCs. But once you start learning clean code, or begin to understand the SOLID principles of application design, you would just never be as happy with Data Annotations as you were before. It is not a good approach to combine your models and validation logic.
Using data annotations ties your validation rules to your model classes. This can violate the separation of concerns principle, as validation logic is mixed with your domain logic.
So, what’s the solution?
Introducing FluentValidation - The Solution
FluentValidation is a powerful open-source .NET validation library that helps you make your validations clean, easy to create, and maintain. It helps you build strongly typed validation rules. It even works on external models that you don’t have access to. With this library, you can separate the model classes from the validation logic as it is supposed to be. It doesn’t make your classes cluttered like Data Annotations do. Also, better control of validation is something that makes the developers prefer FluentValidation.
FluentValidation provides a fluent interface for defining validation rules, allowing you to express complex validation logic clearly and concisely. This makes it easy to create custom validation rules and handle complex validation scenarios.
For smaller projects, I would recommend just using Data Annotations because they’re so easy to set up. For larger, more complex systems, FluentValidation is the way to go, due to the above-mentioned benefits.
Using FluentValidation in ASP.NET Core Applications
For this demonstration, I will be using an ASP.NET Core 8 Web API project, and my IDE as Visual Studio 2022 Community. We will do the API testing using Swagger.
Open up Visual Studio and create a new .NET 8 Web API project. Ensure that you have enabled Swagger, as we will be testing our API with this.
Installing FluentValidation Package
Let’s get started by installing the required FluentValidation NuGet packages to your project via the Package Manage Console.
Note that the
FluentValidation.AspNetCore
package is now deprecated.
User Registration Request
For the demo, we will take the scenario where our API is responsible for registering new users into the system by exposing a POST API endpoint. Here is what the C# class would look like.
Our end goal is to validate this request class whenever the registration API is hit. For example, we need to ensure that the user has entered a valid FirstName
, Email
and so on. We will build up on this by introducing various features offered by FluentValidation.
Adding Validator Rules
In this step, we will define the validation rules of our UserRegistrationRequest
. Create a new folder named Validators and add a new class named UserRegistrationValidator
To begin with, we will add a single validation rule that validates if the passed Email property is an Email Address.
Note that the UserRegistrationValidator
inherits from AbstractValidator<T>
where T is the actual class to be validated.
Registering Validators in ASP.NET Core
We will have to add FluentValidation to our application. Open up Program.cs
.
Now there are several ways to register the UserRegistrationValidator
into the application’s DI Container.
If you want to register just a single validator, you can use the following.
This ensures that you can inject IValidator<UserRegistrationRequest>
into your constructors and validate as required.
In case your project has multiple validators, and you don’t want to register them manually one by one, you can use the following.
In the above code, FluentValidation registers all the validator instances that are found in the assembly containing UserRegistrationValidator
.
Approaches for Validation
FluentValidation
validates incoming models in different ways, like
- Manual Validation - This is the recommended approach.
- Automatics Validation using ASP.NET Core Pipeline - This is deprecated and no longer supported in the latest release of FluentValidation, version
11.9.1
. Read more about the breaking change from this discussion. - Validation using Filters - requires external packages.
In this demo, we will learn about Manual Validation using the FluentValidation package.
API Endpoint
Now, we will create a Minimal API endpoint that allows one to register a new user into the system. This will be a POST endpoint and will accept a body of type UserRegistrationRequest
.
Note that we have injected an instance of IValidator<UserRegistrationRequest>
to the endpoint which we will use for validating the incoming object.
We first use the validator object to asynchronously validate the UserRegistrationRequest
object. If the result is valid, the endpoint returns an Accepted status code. Otherwise, Problem Details of Type Validation in returned. Let’s see this in action.
Build and run your .NET 8 Web API, and open up Swagger.
I am going to invoke the API by passing the above default values. As you see, we have specified an invalid email id. If you execute the request, you will be able to see the following response.
As you see, we got a 400 Bad Request response with validation errors that include the 'Email' is not a valid email address.
error message. If you pass a valid email address to the request, you will be able to see that this validation error goes away. Note that this is capable of returning an array of errors if required.
Extending the Validation Rules
In the next steps, we will further explore the capabilities of FluentValidation
and how it can help build powerful validations in our system.
Custom Validation Messages
It’s highly practical to show custom validation messages based on the property. It’s quite simple with FluentValidation. Go to the validator that we created and add an extension WithMessage()
to the rule associated with the Email property.
Use
{PropertyName}
to get the corresponding Property Name. These are placeholder variables of FluentValidation Library. Read more about them here.
Let’s check out the response now.
Built-In Validators
There are several validators you get by default with FluentValidation. We will use some of them to write additional validations to the other properties of the class.
Let me explain what each rule validates.
FirstName
should not be empty and have a minimum string length of 4.LastName
also should not be empty and should have a length with a maximum of 10 characters.- Email validation as we saw earlier.
- This ensures that the
Password
andConfirmPassword
fields match in the incoming request.
Overriding Property Name
You also get the provision to change the name of the property returned to the validation messages. For instance, if you want to change the property name of Email
to MailID
, you can simply add the WithName
extension.
And here is the response from our ASP.NET Core Web API:
Chaining Validators
There can be cases where you want to add more than validation to a particular property. The below rule has 2 such validations in the FirstName
property. It should not be empty, and at the same time the minimum length of the property has to be at least 4 characters.
Stop on First Failure
You can also customize how FluentValidation executes the validation checks. For instance, if you consider the above code, where we have 2 validations bound to the same rule, and you pass an empty string as the value for FirstName
, you will be seeing 2 validation messages in the response.
In certain cases, you want the validation checks to stop as soon as the first validation breaks for a property. In our case, we might need to stop the validation check right at the NotEmpty
validator.
To achieve this, we have the Cascade property which allows 2 modes,
- Continue - This is the default behavior, which continues to invoke the validations even though a previous validation has failed.
- Stop - Stops executing validation checks as soon as the first validation breaks.
You can modify your code as below,
Custom Validations
As an example, theoretically, you could pass a number as the first name and the API would just say ‘Cool, that’s fine’. Let’s see how to build a validation rule with Fluent Validation for such a scenario.
First, create a simple helper method that would take in our FirstName
property and return a boolean based on its content. We will place this method in our UserRegistrationValidator
class itself.
Now, let’s add this requirement to the rule and wire it up with our helper method.
Line #5 suggests that the property with return a true when passed to our helper.
Now, if I pass a number to the FirstName
property as part of the request, I will get the following response from my API.
So, that is taken care of as well.
Enhancing with MediatR Pipeline Behavior
You might have already seen a problem with our implementation. In our API endpoint code we are always trying to validate the request first, before proceeding to the actual application logic. Is there a way to remove the below code and let you app handle validation by itself?
To solve this, we can use MediatR Pipeline Behavior, a middleware that can automatically execute the validation mechanism as soon as the request enters into the application pipeline. This way you no longer need to write the validation calls to each and every API endpoint. Ideally, this is how you would have to build your applications!
You can read the article here : Validation with MediatR Pipeline Behavior and FluentValidation - Handling Validation Exceptions in ASP.NET Core
Summary
I hope you have enjoyed this simple-to-follow guide about FluentValidation in ASP.NET Core. I am sure you will be switching to this powerful library from today onwards! What are your opinions about this library? Some devs still prefer Data Annotations over this. What about you? Let me know in the comments below.