Integration of FluentValidation with Minimal API in .NET
This is the last article in the series dedicated to FluentValidation. We will start by introducing two fundamental methods of registering validators in .NET applications using the dependency injection mechanism. We will discuss methods that allow the registration of dedicated validators and the use of the FluentValidation.DependencyInjectionExtensions package, which greatly simplifies and automates the process of registering validators.
Additionally, we will show how to use registered validators in Minimal API, allowing for clear and effective management of input data validation in modern projects. The article will also cover managing the lifecycle of registered validators. Let's dive in.
Registering a Specific Validator
FluentValidation validators can be easily integrated with various dependency injection libraries, including the popular Microsoft.Extensions.DependencyInjection library. The process of integrating a validator for a specific data model type is simple and straightforward. It involves registering the validator in the service provider. The key step is to declare the validator as a type of IValidator<T>, where T represents the specific type of object to be validated. This approach makes the validator available throughout the system as a service, allowing for easy and flexible management of data validation processes.
builder.Services.AddTransient<IValidator<SomeModel>, SomeModelValidator>();
In the example above, SomeModelValidator is explicitly registered as the validator for the SomeModel type.
Using the FluentValidation.DependencyInjectionExtensions package greatly simplifies the process of registering validators by offering extension methods that automate the scanning and registration of validators in the service container. To use this method, first add the package to your project:
dotnet add package FluentValidation.DependencyInjectionExtensions
After adding the package, extension methods such as AddValidatorsFromAssemblyContaining become available, allowing for the automated registration of all validators defined in a given assembly. You can choose from several overloads of this method to tailor the registration process to your needs, including the ability to set the validator's lifetime to Singleton, Scoped, or Transient.
// Overload 1 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining(typeof(StudentValidator));
// Overload 2 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>();
// Overload 3 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>(ServiceLifetime.Scoped);
Using these methods, you can greatly simplify the management of validations in your applications while maintaining high flexibility and control over the process.
Validator Lifetime
In general, it is recommended to register FluentValidation validators as Transient, which is the safest and simplest option. This scope ensures that each validation operation uses a new, independent instance of the validator, thus eliminating the risk associated with sharing state between operations.
Registering a validator as Singleton requires special care. In such cases, we should avoid injecting Transient or Scoped dependencies into the validator, as this can lead to complex problems related to managing the state and lifecycle of objects. Generally, registering validators as Singleton is not recommended unless you have experience with dependency injection and know how to address issues arising from having dependencies with different lifetimes in Singleton objects.
In summary, while it is possible to set different lifetimes for validators, the most commonly suggested and safest approach is to use the Transient scope, especially if you want to avoid potential pitfalls related to dependency management.
Using FluentValidation in Minimal API
After registering the validators, we can easily use them directly in Minimal API endpoints. Below is a sample implementation that shows how to use a registered validator in Minimal API. First, let's define the User model and the UserValidator:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.FirstName).NotEmpty().WithMessage("First name is required");
RuleFor(x => x.LastName).NotEmpty().WithMessage("Last name is required");
RuleFor(x => x.Email).EmailAddress().WithMessage("A valid email is required");
RuleFor(x => x.Age).InclusiveBetween(18, 60).WithMessage("Age must be between 18 and 60");
}
}
Let's add an endpoint to the Minimal API that uses the registered validator:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidatorsFromAssemblyContaining<UserValidator>();
var app = builder.Build();
app.MapPost("/users", async (IValidator<User> validator, User user) =>
{
ValidationResult validationResult = await validator.ValidateAsync(user);
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.ToDictionary());
}
// ...
});
app.Run();
The code above shows how to use FluentValidation in Minimal API. Validation takes place directly in the API endpoints, allowing for clear and easy management of input data validation. In case of validation failure, the API returns an appropriate response with validation errors.
Summary
The article presented methods of integrating FluentValidation validators with .NET applications using the dependency injection mechanism. Practical aspects of this integration were discussed, highlighting how it facilitates managing validations in a project. The importance of registering dedicated validators and the benefits of using the FluentValidation.DependencyInjectionExtensions package, which automates and simplifies the integration process, were emphasized.
The article also stressed the importance of managing validator lifetimes properly, recommending the Transient scope for maximum reliability and minimizing the risk of shared state errors. The potential pitfalls associated with using the Singleton scope and the need to avoid mixing dependency lifetimes were outlined.
Additionally, the details of integrating FluentValidation with Minimal API were discussed, showing how to use registered validators directly in endpoints. Code examples illustrated how to perform input data validation in Minimal API and how to appropriately respond to validation errors by returning the relevant HTTP responses.
In conclusion, by thoroughly understanding the discussed concepts and effectively utilizing the available tools, you can efficiently implement FluentValidation validators, resulting in robustness, consistency, and ease of managing validation processes in .NET Core applications, including modern projects based on Minimal API.