In an ASP.NET Core Web API application, validating incoming data is an important part of ensuring data consistency and application reliability.
For example, when creating an employee, we may need to validate the following information:- Employee name should not be empty.
- Email address should be in a valid format.
- Salary should be greater than zero.
- Required fields should contain valid values.
We can implement these validations directly inside API controllers. However, as an application grows, maintaining validation logic inside controllers can make them difficult to manage and test.
In this article, we will learn how to implement validation using FluentValidation in an ASP.NET Core Clean Architecture application.
We will use our existing CleanArchitectureDemo project and cover:- Understanding FluentValidation.
- Installing the required NuGet packages.
- Creating validation classes.
- Implementing validation for Employee DTOs.
- Registering validators with dependency injection.
- Integrating validation into the API layer.
- Testing validation using Swagger.
ASP.NET Clean Architecture: Implement Validation Using FluentValidation
What Is FluentValidation?
FluentValidation is a .NET library that provides a fluent API for defining validation rules for application models.
Instead of writing validation conditions inside controllers or DTO classes, we can create separate validator classes to define validation rules.
For example, consider an Employee DTO:</> C#
public class EmployeeDto
{
public string Name { get; set; }
public string Email { get; set; }
public decimal Salary { get; set; }
}
Using FluentValidation, we can define validation rules in a separate class:
</> C#
public class EmployeeDtoValidator
: AbstractValidator<EmployeeDto>
{
public EmployeeDtoValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Employee name is required.");
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.WithMessage("Enter a valid email address.");
RuleFor(x => x.Salary)
.GreaterThan(0)
.WithMessage("Salary must be greater than zero.");
}
}
This approach keeps validation logic separate from business logic and makes the application easier to maintain and test.
Where Does Validation Belong in Clean Architecture?
We will create validator classes in the Application project because they validate application input models and use cases.
The API layer will invoke these validators and return appropriate validation error responses.
It is also important to distinguish input validation from domain invariants. Rules that must always hold true for an entity should still be protected by the Domain layer.
Install the Required NuGet Packages
We will use the following NuGet packages:FluentValidation– Defines validation rules.FluentValidation.DependencyInjectionExtensions– Registers validators using dependency injection.
Install the libraries in the following way
FluentValidation: Install in theCleanArchitectureDemo.Applicationproject to define validation rules.FluentValidation.DependencyInjectionExtensions: in theCleanArchitectureDemo.APIproject to register the validators using dependency injection.
</> powershell
Install-Package FluentValidation -ProjectName CleanArchitectureDemo.Application
Install-Package FluentValidation.DependencyInjectionExtensions -ProjectName CleanArchitectureDemo.API
Version Note: For your .NET 8 or .NET 10 implementation, use FluentValidation 12. If readers are using older target frameworks, they should select a compatible FluentValidation version.
Create an Employee DTO Validator
Now that we have installed the required packages, let's create a validator for the Employee DTO.
- Create a Validators folder: Open the
CleanArchitectureDemo.Applicationproject and create a folder namedValidators. - Create
EmployeeDtoValidator.cs: Add a new class namedEmployeeDtoValidator.csinside the Validators folder.</> C#
using CleanArchitectureDemo.Application.DTOs; using FluentValidation; namespace CleanArchitectureDemo.Application.Validators { public class EmployeeDtoValidator : AbstractValidator<EmployeeDto> { public EmployeeDtoValidator() { RuleFor(x => x.Name) .NotEmpty() .WithMessage("Employee name is required.") .MaximumLength(100) .WithMessage("Employee name cannot exceed 100 characters."); RuleFor(x => x.Email) .NotEmpty() .WithMessage("Email address is required.") .EmailAddress() .WithMessage("Enter a valid email address."); RuleFor(x => x.Salary) .GreaterThan(0) .WithMessage("Salary must be greater than zero."); } } }
Let's understand the FluentValidation methods used in this example:RuleFor()NotEmpty()MaximumLength()EmailAddress()GreaterThan()WithMessage()
Register FluentValidation in the API Project
After creating the validator, we need to register it with the dependency injection container so that it can be injected wherever required.
OpenProgram.cs in the CleanArchitectureDemo.API project. Add the following namespace:
</> C#
using FluentValidation;
using CleanArchitectureDemo.Application.Validators;
Inside the service registration section, add:
</> C#
builder.Services.AddValidatorsFromAssemblyContaining<EmployeeDtoValidator>();
This scans the assembly containing EmployeeDtoValidator and registers the validators it discovers.
You don't need to register every validator manually. When you add additional validators to the Application project, they can also be discovered through this registration.
Integrate FluentValidation into the Employee API
Now we need to invoke the validator before processing an incoming API request.
For new ASP.NET Core applications, I recommend manual validation using ValidateAsync() rather than relying on the older automatic MVC validation pipeline. This approach supports asynchronous validation and makes the validation process explicit.
Open EmployeesController.cs in the API project.
Add an IValidator<EmployeeDto> dependency to the controller.
</> C#
private readonly IValidator<EmployeeDto> _validator;
public EmployeesController(
IEmployeeService employeeService,
IValidator<EmployeeDto> validator)
{
_employeeService = employeeService;
_validator = validator;
}
Keep your existing service dependency and other constructor parameters as they are.
Step 2: Validate the employee dataUpdate the POST API endpoint to validate the incoming employee data before calling the application service.
</> C#
[HttpPost]
public async Task<IActionResult> Create(EmployeeDto employeeDto)
{
var validationResult =
await _validator.ValidateAsync(employeeDto);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors
.GroupBy(x => x.PropertyName)
.ToDictionary(
x => x.Key,
x => x.Select(e => e.ErrorMessage).ToArray()
);
return BadRequest(
new ValidationProblemDetails(errors));
}
await _employeeService.AddAsync(employeeDto);
return Ok("Employee created successfully.");
}
Note: Adjust the service method and DTO types to match the signatures in your existing implementation.
How does this work?- The API receives the employee information.
- The controller invokes
ValidateAsync()to execute the registered validation rules. - If validation fails, the API returns HTTP 400 (Bad Request) with the validation error messages.
- If validation succeeds, the controller calls the application service to process the request.
This ensures that invalid employee information is rejected before the application service performs its operation.
Test Validation Using Swagger
Now let's test the validation rules using Swagger. Run the CleanArchitectureDemo.API project and open Swagger UI.
Expand the POST /api/employees endpoint and click Try it out.
</> JSON
{
"name": "",
"email": "invalid-email",
"salary": -5000
}
Expected result:
400 Bad Request
</> JSON
{
"errors": {
"Name": [
"Employee name is required."
],
"Email": [
"Enter a valid email address."
],
"Salary": [
"Salary must be greater than zero."
]
}
}
Test 2: Submit valid employee information
Use the following request body:
</> JSON
{
"name": "Kailash",
"email": "kailash@example.com",
"salary": 50000
}
Click Execute
Expected result:
200 OK
The employee should be successfully created. The exact response body depends on your controller implementation.
Benefits of Using FluentValidation in Clean Architecture
Using FluentValidation provides several benefits:- Separation of concerns: Validation logic stays separate from controllers and business logic.
- Reusability: The same validation rules can be reused wherever the corresponding DTO is validated.
- Maintainability: Validation rules can be modified without changing API controller code.
- Testability: Validator classes can be tested independently using unit tests.
- Readable code: Fluent API methods make validation rules easy to understand.
One additional improvement you can demonstrate is creating separate validators forCreateEmployeeDto and UpdateEmployeeDto when their validation requirements differ.
Summary
In this article, we learned how to implement validation in an ASP.NET Core Clean Architecture application using FluentValidation
We created an Employee DTO validator, defined validation rules, registered validators using dependency injection, and integrated validation into the API controller.
We also tested the validation rules using Swagger to verify that invalid employee information is rejected before processing.
By using FluentValidation, we can keep our validation logic organized, reusable, and easier to maintain as our application grows.
Thanks