Barış Kısır

Elevating Object Validation with FluentValidation in .NET

10 Dec 2016

The Pitfalls of Imperative Validation

In traditional application development, validation logic is often intertwined with business logic through exhaustive if-else blocks or switch statements. This imperative approach not only clutters the codebase but also makes the validation rules difficult to maintain, test, and reuse.

Introducing FluentValidation

FluentValidation is a popular .NET library that utilizes a fluent interface and lambda expressions for building strongly-typed validation rules. By decoupling the validation logic from the domain models, it promotes a cleaner architecture and enhances code maintainability.

Installation via NuGet

To integrate FluentValidation into your project, execute the following command in the Package Manager Console:

Install-Package FluentValidation

Architecting the Validator

Consider an Employee model. Instead of polluting the model with validation attributes, we create a dedicated validator class that inherits from AbstractValidator<T>.

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

public class EmployeeValidator : AbstractValidator<Employee>
{
    public EmployeeValidator()
    {
        // Defining complex rules with meaningful error messages
        RuleFor(x => x.Age)
            .InclusiveBetween(18, 100)
            .WithMessage("Employee must be between 18 and 100 years of age.");

        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(50)
            .WithMessage("A valid Name is required.");
            
        RuleFor(x => x.Email)
            .EmailAddress()
            .WithMessage("The provided Email Address is invalid.");
    }
}

Orchestrating the Validation Process

The following snippet demonstrates how to instantiate the validator and process the results. This pattern is ideal for clean UI feedback or API error responses.

Employee employee = new Employee 
{ 
    Name = txtName.Text, 
    Age = int.TryParse(txtAge.Text, out var age) ? age : 0, 
    Email = txtEmail.Text 
};

var validator = new EmployeeValidator();
var results = validator.Validate(employee);

if (!results.IsValid)
{
    // Aggregate and display validation failures
    lblError.Text = string.Join("<br />", results.Errors.Select(e => e.ErrorMessage));
}
else
{
    lblError.Text = "Validation successful. Proceeding with operation.";
}

Strategic Advantages

  1. Separation of Concerns: Keeps your domain models focused solely on data and behavior, moving validation to a dedicated layer.
  2. Testability: Because validators are isolated classes, they can be unit-tested thoroughly without instantiating complex dependencies.
  3. Readability: The fluent API makes the validation rules self-documenting and easy to audit for business compliance.

Explore the Implementation: Detailed source code and usage patterns are available on GitHub.