FluentValidation

Validation LibraryC#.NETEnterpriseHigh PerformanceDomain Driven Design

Library

FluentValidation

Overview

FluentValidation is the most trusted validation library in the C#/.NET ecosystem. Developed with the philosophy of "simple, readable, strongly-typed validation rules," it provides the most powerful and flexible validation experience for .NET developers. With over 600 million downloads, deep integration with ASP.NET Core, Entity Framework, and Blazor makes it the standard choice for enterprise-level application development.

Details

FluentValidation 12.0.0 is the latest version as of 2025, focusing on legacy code removal and ending support for obsolete platforms. Supporting .NET 8 and later, it provides a powerful type-safe validation system based on Linq Expressions. The AbstractValidator-based class design integrates seamlessly with Domain-Driven Design (DDD) patterns, intuitively implementing complex business rules. Designed for singleton pattern usage, it maintains high performance.

Key Features

  • Strong Type Safety: Maximum utilization of C#'s type system and Lambda expressions
  • ASP.NET Core Integration: Unified validation across MVC, Web API, and Blazor
  • Enterprise Ready: Designed for complex business rules and large-scale projects
  • High Performance: Optimized with singleton pattern and Expression Trees
  • Rich Validators: 40+ built-in validation features
  • Highly Customizable: Easy custom validator and rule extension

Pros and Cons

Pros

  • Overwhelming track record and reliability in .NET ecosystem (600+ million downloads)
  • Perfect integration with ASP.NET Core, Entity Framework, and Blazor
  • Rich adoption cases in enterprise-level applications
  • Strong type safety and development efficiency through IntelliSense
  • Advanced features like conditional validation, rule sets, and inheritance support
  • Comprehensive official documentation and active community support

Cons

  • C#/.NET only (not usable in other languages)
  • Somewhat high initial learning curve (requires understanding Expression Trees)
  • May be feature-rich for lightweight validation needs
  • Additional configuration needed for frontend (JavaScript) integration
  • Performance tuning may be required for complex business rules
  • Support ended for older .NET Framework versions

Reference Pages

Code Examples

Installation and Basic Setup

# Install FluentValidation
dotnet add package FluentValidation

# ASP.NET Core integration (note deprecated package)
dotnet add package FluentValidation.AspNetCore

# Using NuGet Package Manager
Install-Package FluentValidation
Install-Package FluentValidation.AspNetCore

Basic Schema Definition and Validation

using FluentValidation;

// Customer class definition
public class Customer
{
    public int Id { get; set; }
    public string Surname { get; set; }
    public string Forename { get; set; }
    public decimal Discount { get; set; }
    public string Address { get; set; }
    public bool HasDiscount { get; set; }
    public string Postcode { get; set; }
}

// Basic validator class
public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        // Basic validation rules
        RuleFor(customer => customer.Surname)
            .NotEmpty()
            .WithMessage("Surname is required");
        
        RuleFor(customer => customer.Forename)
            .NotEmpty()
            .WithMessage("Please specify a first name")
            .Length(2, 50)
            .WithMessage("First name must be between 2 and 50 characters");
        
        RuleFor(customer => customer.Discount)
            .NotEqual(0)
            .When(customer => customer.HasDiscount)
            .WithMessage("Please set discount rate when discount is enabled");
        
        RuleFor(customer => customer.Address)
            .Length(20, 250)
            .WithMessage("Address must be between 20 and 250 characters");
        
        RuleFor(customer => customer.Postcode)
            .Must(BeAValidPostcode)
            .WithMessage("Please specify a valid postcode");
    }
    
    // Custom validation method
    private bool BeAValidPostcode(string postcode)
    {
        if (string.IsNullOrEmpty(postcode))
            return false;
        
        // US postal code format (e.g., 12345-6789)
        return System.Text.RegularExpressions.Regex.IsMatch(
            postcode, @"^\d{5}-\d{4}$");
    }
}

// Validation execution
class Program
{
    static void Main()
    {
        var customer = new Customer
        {
            Surname = "",
            Forename = "John",
            HasDiscount = true,
            Discount = 0,
            Address = "Too short",
            Postcode = "invalid"
        };
        
        var validator = new CustomerValidator();
        
        // Execute validation
        var results = validator.Validate(customer);
        
        if (!results.IsValid)
        {
            foreach (var failure in results.Errors)
            {
                Console.WriteLine($"Property {failure.PropertyName} failed validation. Error: {failure.ErrorMessage}");
            }
        }
        
        // Validation with exception throwing
        try
        {
            validator.ValidateAndThrow(customer);
        }
        catch (ValidationException ex)
        {
            Console.WriteLine($"Validation error: {ex.Message}");
        }
    }
}

Advanced Validation Rules and Custom Validators

using FluentValidation;
using FluentValidation.Results;

// Complex product order model
public class ProductOrder
{
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal DiscountRate { get; set; }
    public DateTime OrderDate { get; set; }
    public Address ShippingAddress { get; set; }
    public List<string> Tags { get; set; } = new List<string>();
    public OrderStatus Status { get; set; }
    public Customer Customer { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; } = "USA";
}

public enum OrderStatus
{
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled
}

// Advanced validator class
public class ProductOrderValidator : AbstractValidator<ProductOrder>
{
    public ProductOrderValidator()
    {
        // Detailed product name validation
        RuleFor(order => order.ProductName)
            .NotEmpty()
            .WithMessage("Product name is required")
            .Length(2, 100)
            .WithMessage("Product name must be between 2 and 100 characters")
            .Matches(@"^[a-zA-Z0-9\s\-_]+$")
            .WithMessage("Product name can only contain alphanumeric characters, spaces, hyphens, and underscores");
        
        // Conditional quantity validation
        RuleFor(order => order.Quantity)
            .GreaterThan(0)
            .WithMessage("Quantity must be greater than 0")
            .LessThanOrEqualTo(1000)
            .WithMessage("Maximum order quantity is 1000 items")
            .Must(BeValidQuantityForProduct)
            .WithMessage("Order quantity exceeds the limit for this product");
        
        // Detailed price validation
        RuleFor(order => order.UnitPrice)
            .GreaterThan(0)
            .WithMessage("Unit price must be greater than 0")
            .LessThanOrEqualTo(1000000)
            .WithMessage("Unit price must be less than or equal to $1,000,000")
            .ScalePrecision(2, 10)
            .WithMessage("Unit price can have up to 2 decimal places");
        
        // Discount rate validation
        RuleFor(order => order.DiscountRate)
            .InclusiveBetween(0, 1)
            .WithMessage("Discount rate must be between 0 and 1");
        
        // Order date validation
        RuleFor(order => order.OrderDate)
            .GreaterThanOrEqualTo(DateTime.Today.AddDays(-30))
            .WithMessage("Order date must be within the last 30 days")
            .LessThanOrEqualTo(DateTime.Today.AddDays(7))
            .WithMessage("Order date cannot be more than 7 days in the future");
        
        // Nested object validation
        RuleFor(order => order.ShippingAddress)
            .SetValidator(new AddressValidator())
            .When(order => order.Status != OrderStatus.Cancelled);
        
        // Collection validation
        RuleFor(order => order.Tags)
            .Must(tags => tags.Count <= 5)
            .WithMessage("Maximum of 5 tags allowed");
        
        RuleForEach(order => order.Tags)
            .NotEmpty()
            .WithMessage("Empty tags are not allowed")
            .Length(2, 20)
            .WithMessage("Each tag must be between 2 and 20 characters");
        
        // Conditional validation (multiple conditions)
        When(order => order.Status == OrderStatus.Processing, () =>
        {
            RuleFor(order => order.Customer)
                .NotNull()
                .WithMessage("Customer information is required for processing orders");
            
            RuleFor(order => order.ShippingAddress)
                .NotNull()
                .WithMessage("Shipping address is required for processing orders");
        }).Otherwise(() =>
        {
            RuleFor(order => order.Status)
                .NotEqual(OrderStatus.Shipped)
                .WithMessage("Cannot set status to shipped without customer information");
        });
        
        // Dependent rules (only execute if prerequisite rules pass)
        RuleFor(order => order.ProductName)
            .NotEmpty()
            .DependentRules(() =>
            {
                RuleFor(order => order.Quantity)
                    .Must((order, quantity) => IsQuantityAvailableForProduct(order.ProductName, quantity))
                    .WithMessage("Insufficient stock for the specified product");
            });
        
        // Rule set definitions
        RuleSet("QuickValidation", () =>
        {
            RuleFor(order => order.ProductName).NotEmpty();
            RuleFor(order => order.Quantity).GreaterThan(0);
        });
        
        RuleSet("CompleteValidation", () =>
        {
            Include("QuickValidation");
            RuleFor(order => order.UnitPrice).GreaterThan(0);
            RuleFor(order => order.ShippingAddress).SetValidator(new AddressValidator());
        });
    }
    
    // Custom validation methods
    private bool BeValidQuantityForProduct(int quantity)
    {
        // Product-specific quantity limits logic
        return quantity <= 100; // Simple example
    }
    
    private bool IsQuantityAvailableForProduct(string productName, int quantity)
    {
        // Inventory check logic (in real implementation, would access database)
        return !string.IsNullOrEmpty(productName) && quantity > 0;
    }
}

// Address validator
public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(address => address.Street)
            .NotEmpty()
            .WithMessage("Street address is required")
            .Length(5, 200)
            .WithMessage("Street address must be between 5 and 200 characters");
        
        RuleFor(address => address.City)
            .NotEmpty()
            .WithMessage("City is required");
        
        RuleFor(address => address.PostalCode)
            .NotEmpty()
            .WithMessage("Postal code is required")
            .Matches(@"^\d{5}-\d{4}$")
            .WithMessage("Postal code must be in format 00000-0000");
        
        RuleFor(address => address.Country)
            .NotEmpty()
            .WithMessage("Country is required");
    }
}

Framework Integration (ASP.NET Core, Blazor, Entity Framework, etc.)

using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

// ASP.NET Core integration example
// Startup.cs or Program.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register FluentValidation (recommended approach)
        services.AddValidatorsFromAssemblyContaining<CustomerValidator>();
        
        // Individual validator registration
        services.AddScoped<IValidator<Customer>, CustomerValidator>();
        services.AddScoped<IValidator<ProductOrder>, ProductOrderValidator>();
        
        // ASP.NET Core MVC configuration
        services.AddControllers(options =>
        {
            // Disable automatic model validation (prefer FluentValidation)
            options.ModelValidatorProviders.Clear();
        });
    }
}

// ASP.NET Core Web API usage example
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
    private readonly IValidator<Customer> _customerValidator;
    
    public CustomersController(IValidator<Customer> customerValidator)
    {
        _customerValidator = customerValidator;
    }
    
    [HttpPost]
    public async Task<IActionResult> CreateCustomer([FromBody] Customer customer)
    {
        // Execute validation
        var validationResult = await _customerValidator.ValidateAsync(customer);
        
        if (!validationResult.IsValid)
        {
            // Add errors to model state
            foreach (var error in validationResult.Errors)
            {
                ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
            }
            
            return BadRequest(ModelState);
        }
        
        // Processing when validation succeeds
        // Save customer to database here
        
        return Ok(new { message = "Customer created successfully", customer });
    }
    
    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateCustomer(int id, [FromBody] Customer customer)
    {
        // Execute only specific rule sets
        var validationResult = await _customerValidator.ValidateAsync(customer, options =>
        {
            options.IncludeRuleSets("UpdateValidation");
            options.IncludeProperties(x => x.Surname, x => x.Forename);
        });
        
        if (!validationResult.IsValid)
        {
            return BadRequest(validationResult.Errors.Select(e => new
            {
                Property = e.PropertyName,
                Error = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue
            }));
        }
        
        return Ok();
    }
}

// Blazor usage example
@page "/customer-form"
@using FluentValidation
@inject IValidator<Customer> CustomerValidator

<EditForm Model="@customer" OnValidSubmit="@HandleValidSubmit">
    <FluentValidationValidator />
    <ValidationSummary />
    
    <div class="form-group">
        <label for="surname">Surname:</label>
        <InputText id="surname" @bind-Value="customer.Surname" class="form-control" />
        <ValidationMessage For="@(() => customer.Surname)" />
    </div>
    
    <div class="form-group">
        <label for="forename">First Name:</label>
        <InputText id="forename" @bind-Value="customer.Forename" class="form-control" />
        <ValidationMessage For="@(() => customer.Forename)" />
    </div>
    
    <button type="submit" class="btn btn-primary">Save</button>
</EditForm>

@code {
    private Customer customer = new Customer();
    
    private async Task HandleValidSubmit()
    {
        var validationResult = await CustomerValidator.ValidateAsync(customer);
        
        if (validationResult.IsValid)
        {
            // Save processing
            Console.WriteLine("Customer information saved");
        }
    }
}

// Entity Framework integration example
public class CustomerService
{
    private readonly IValidator<Customer> _validator;
    private readonly ApplicationDbContext _context;
    
    public CustomerService(IValidator<Customer> validator, ApplicationDbContext context)
    {
        _validator = validator;
        _context = context;
    }
    
    public async Task<Result<Customer>> CreateCustomerAsync(Customer customer)
    {
        // Execute validation
        var validationResult = await _validator.ValidateAsync(customer);
        
        if (!validationResult.IsValid)
        {
            return Result<Customer>.Failure(validationResult.Errors.Select(e => e.ErrorMessage));
        }
        
        // Database save
        try
        {
            _context.Customers.Add(customer);
            await _context.SaveChangesAsync();
            
            return Result<Customer>.Success(customer);
        }
        catch (Exception ex)
        {
            return Result<Customer>.Failure($"Database error: {ex.Message}");
        }
    }
}

// Result class
public class Result<T>
{
    public bool IsSuccess { get; set; }
    public T Data { get; set; }
    public IEnumerable<string> Errors { get; set; }
    
    public static Result<T> Success(T data) => new Result<T> 
    { 
        IsSuccess = true, 
        Data = data, 
        Errors = Enumerable.Empty<string>() 
    };
    
    public static Result<T> Failure(IEnumerable<string> errors) => new Result<T> 
    { 
        IsSuccess = false, 
        Errors = errors 
    };
    
    public static Result<T> Failure(string error) => Failure(new[] { error });
}

Error Handling and Custom Error Messages

using FluentValidation;
using FluentValidation.Results;

// Detailed error handling example
public class AdvancedCustomerValidator : AbstractValidator<Customer>
{
    public AdvancedCustomerValidator()
    {
        // Custom error messages
        RuleFor(customer => customer.Surname)
            .NotEmpty()
            .WithMessage("Surname is required")
            .WithErrorCode("SURNAME_REQUIRED")
            .WithSeverity(Severity.Error);
        
        RuleFor(customer => customer.Forename)
            .NotEmpty()
            .WithMessage("First name is required")
            .Length(2, 50)
            .WithMessage("First name must be between {MinLength} and {MaxLength} characters")
            .WithErrorCode("FORENAME_LENGTH");
        
        // Dynamic error messages
        RuleFor(customer => customer.Discount)
            .Must((customer, discount) => ValidateDiscount(customer, discount))
            .WithMessage(customer => $"Discount rate {customer.Discount}% is invalid for customer type '{customer.GetType().Name}'")
            .WithErrorCode("INVALID_DISCOUNT");
        
        // Multi-condition error messages
        RuleFor(customer => customer.Address)
            .NotEmpty()
            .When(customer => customer.HasDiscount)
            .WithMessage("Address is required for discount application")
            .WithState(customer => new { CustomerType = "Premium", RequiresAddress = true });
        
        // Custom state validation
        RuleFor(customer => customer.Postcode)
            .Must(BeValidPostcode)
            .WithMessage("Invalid postcode format")
            .WithState(customer => new ValidationContext
            {
                Timestamp = DateTime.Now,
                UserId = customer.Id,
                ValidationRule = "PostcodeFormat"
            });
    }
    
    private bool ValidateDiscount(Customer customer, decimal discount)
    {
        return discount >= 0 && discount <= 0.5m; // Maximum 50% discount
    }
    
    private bool BeValidPostcode(string postcode)
    {
        return !string.IsNullOrEmpty(postcode) && 
               System.Text.RegularExpressions.Regex.IsMatch(postcode, @"^\d{5}-\d{4}$");
    }
}

// Detailed validation result processing
public class ValidationResultProcessor
{
    public void ProcessValidationResult(ValidationResult result)
    {
        if (result.IsValid)
        {
            Console.WriteLine("Validation successful");
            return;
        }
        
        Console.WriteLine($"Validation error count: {result.Errors.Count}");
        
        // Error classification processing
        var errorsByProperty = result.Errors.GroupBy(e => e.PropertyName);
        
        foreach (var propertyErrors in errorsByProperty)
        {
            Console.WriteLine($"\nProperty: {propertyErrors.Key}");
            
            foreach (var error in propertyErrors)
            {
                Console.WriteLine($"  Error code: {error.ErrorCode}");
                Console.WriteLine($"  Message: {error.ErrorMessage}");
                Console.WriteLine($"  Attempted value: {error.AttemptedValue}");
                Console.WriteLine($"  Severity: {error.Severity}");
                
                if (error.CustomState != null)
                {
                    Console.WriteLine($"  Custom state: {error.CustomState}");
                }
                Console.WriteLine();
            }
        }
    }
    
    public Dictionary<string, List<string>> GetErrorDictionary(ValidationResult result)
    {
        return result.Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(
                g => g.Key,
                g => g.Select(e => e.ErrorMessage).ToList()
            );
    }
    
    public bool HasCriticalErrors(ValidationResult result)
    {
        return result.Errors.Any(e => e.Severity == Severity.Error);
    }
}

// Custom validation context
public class ValidationContext
{
    public DateTime Timestamp { get; set; }
    public int UserId { get; set; }
    public string ValidationRule { get; set; }
    
    public override string ToString()
    {
        return $"Validation time: {Timestamp:yyyy-MM-dd HH:mm:ss}, " +
               $"User ID: {UserId}, " +
               $"Rule: {ValidationRule}";
    }
}

// Global validation configuration
public static class ValidationConfiguration
{
    public static void ConfigureGlobalSettings()
    {
        // Global cascade mode configuration
        ValidatorOptions.Global.DefaultClassLevelCascadeMode = CascadeMode.Continue;
        ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
        
        // Default severity configuration
        ValidatorOptions.Global.Severity = Severity.Error;
        
        // Custom display name resolver configuration
        ValidatorOptions.Global.DisplayNameResolver = (type, member, expression) =>
        {
            if (member != null)
            {
                // Property name localization
                return member.Name switch
                {
                    "Surname" => "Surname",
                    "Forename" => "First Name",
                    "Address" => "Address",
                    "Postcode" => "Postal Code",
                    "Discount" => "Discount Rate",
                    _ => member.Name
                };
            }
            return null;
        };
        
        // Custom property name splitting configuration
        ValidatorOptions.Global.PropertyNameResolver = (type, member, expression) =>
        {
            if (expression != null)
            {
                // Nested property display
                return expression.ToString().Replace(".", " of ");
            }
            return member?.Name;
        };
    }
}

// Usage example
class Program
{
    static void Main()
    {
        // Apply global configuration
        ValidationConfiguration.ConfigureGlobalSettings();
        
        var customer = new Customer
        {
            Id = 1,
            Surname = "",
            Forename = "A",
            Discount = 0.6m,
            HasDiscount = true,
            Address = "",
            Postcode = "invalid"
        };
        
        var validator = new AdvancedCustomerValidator();
        var result = validator.Validate(customer);
        
        var processor = new ValidationResultProcessor();
        processor.ProcessValidationResult(result);
        
        // Get error dictionary
        var errorDict = processor.GetErrorDictionary(result);
        Console.WriteLine("\nError dictionary:");
        foreach (var kvp in errorDict)
        {
            Console.WriteLine($"{kvp.Key}: {string.Join(", ", kvp.Value)}");
        }
        
        // Check for critical errors
        bool hasCriticalErrors = processor.HasCriticalErrors(result);
        Console.WriteLine($"\nHas critical errors: {hasCriticalErrors}");
    }
}

Type Safety and Performance Optimization

using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;

// High-performance validator pool
public class ValidatorPool<T> where T : class
{
    private readonly ConcurrentBag<IValidator<T>> _validators;
    private readonly Func<IValidator<T>> _validatorFactory;
    
    public ValidatorPool(Func<IValidator<T>> validatorFactory)
    {
        _validatorFactory = validatorFactory;
        _validators = new ConcurrentBag<IValidator<T>>();
    }
    
    public IValidator<T> Get()
    {
        if (_validators.TryTake(out var validator))
        {
            return validator;
        }
        
        return _validatorFactory();
    }
    
    public void Return(IValidator<T> validator)
    {
        _validators.Add(validator);
    }
}

// Type-safe validation service
public interface IValidationService
{
    Task<ValidationResult<T>> ValidateAsync<T>(T instance) where T : class;
    ValidationResult<T> Validate<T>(T instance) where T : class;
}

public class ValidationService : IValidationService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ConcurrentDictionary<Type, object> _validatorCache;
    
    public ValidationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _validatorCache = new ConcurrentDictionary<Type, object>();
    }
    
    public async Task<ValidationResult<T>> ValidateAsync<T>(T instance) where T : class
    {
        var validator = GetValidator<T>();
        var result = await validator.ValidateAsync(instance);
        
        return new ValidationResult<T>
        {
            Instance = instance,
            IsValid = result.IsValid,
            Errors = result.Errors.Select(e => new ValidationError
            {
                PropertyName = e.PropertyName,
                ErrorMessage = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue,
                ErrorCode = e.ErrorCode,
                Severity = e.Severity
            }).ToList()
        };
    }
    
    public ValidationResult<T> Validate<T>(T instance) where T : class
    {
        var validator = GetValidator<T>();
        var result = validator.Validate(instance);
        
        return new ValidationResult<T>
        {
            Instance = instance,
            IsValid = result.IsValid,
            Errors = result.Errors.Select(e => new ValidationError
            {
                PropertyName = e.PropertyName,
                ErrorMessage = e.ErrorMessage,
                AttemptedValue = e.AttemptedValue,
                ErrorCode = e.ErrorCode,
                Severity = e.Severity
            }).ToList()
        };
    }
    
    private IValidator<T> GetValidator<T>() where T : class
    {
        return (IValidator<T>)_validatorCache.GetOrAdd(typeof(T), _ => 
            _serviceProvider.GetRequiredService<IValidator<T>>());
    }
}

// Type-safe validation result
public class ValidationResult<T> where T : class
{
    public T Instance { get; set; }
    public bool IsValid { get; set; }
    public List<ValidationError> Errors { get; set; } = new();
    
    public bool HasErrorsFor<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        var propertyName = GetPropertyName(propertyExpression);
        return Errors.Any(e => e.PropertyName == propertyName);
    }
    
    public IEnumerable<ValidationError> GetErrorsFor<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        var propertyName = GetPropertyName(propertyExpression);
        return Errors.Where(e => e.PropertyName == propertyName);
    }
    
    private string GetPropertyName<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        if (propertyExpression.Body is MemberExpression memberExpression)
        {
            return memberExpression.Member.Name;
        }
        
        throw new ArgumentException("Expression must be a member expression", nameof(propertyExpression));
    }
}

public class ValidationError
{
    public string PropertyName { get; set; }
    public string ErrorMessage { get; set; }
    public object AttemptedValue { get; set; }
    public string ErrorCode { get; set; }
    public Severity Severity { get; set; }
}

// Performance-optimized validator base class
public abstract class PerformantValidator<T> : AbstractValidator<T>
{
    protected PerformantValidator()
    {
        // Class-level cascade continuation (execute all rules)
        ClassLevelCascadeMode = CascadeMode.Continue;
        
        // Rule-level immediate stop (stop at first failure within individual rules)
        RuleLevelCascadeMode = CascadeMode.Stop;
    }
    
    // Optimization for conditional validation
    protected void WhenNotNull<TProperty>(Expression<Func<T, TProperty>> expression, Action action)
        where TProperty : class
    {
        When(instance => expression.Compile()(instance) != null, action);
    }
    
    // Optimization for conditional validation (value types)
    protected void WhenHasValue<TProperty>(Expression<Func<T, TProperty?>> expression, Action action)
        where TProperty : struct
    {
        When(instance => expression.Compile()(instance).HasValue, action);
    }
}

// Usage example: Optimized customer validator
public class OptimizedCustomerValidator : PerformantValidator<Customer>
{
    public OptimizedCustomerValidator()
    {
        // Basic rules (fast)
        RuleFor(x => x.Surname).NotEmpty().WithErrorCode("SURNAME_REQUIRED");
        RuleFor(x => x.Forename).NotEmpty().WithErrorCode("FORENAME_REQUIRED");
        
        // Conditional rules (optimized)
        WhenNotNull(x => x.Address, () =>
        {
            RuleFor(x => x.Address).Length(10, 200);
        });
        
        // Complex rules (only when necessary)
        RuleFor(x => x.Postcode)
            .Must(BeValidPostcode)
            .When(x => !string.IsNullOrEmpty(x.Postcode))
            .WithErrorCode("INVALID_POSTCODE");
    }
    
    private bool BeValidPostcode(string postcode) =>
        System.Text.RegularExpressions.Regex.IsMatch(postcode ?? "", @"^\d{5}-\d{4}$");
}

// Benchmark test
public class ValidationBenchmark
{
    private readonly Customer _customer;
    private readonly IValidator<Customer> _validator;
    private readonly ValidationService _validationService;
    
    public ValidationBenchmark()
    {
        _customer = new Customer
        {
            Surname = "Smith",
            Forename = "John",
            Address = "123 Main St, New York, NY",
            Postcode = "12345-6789"
        };
        
        _validator = new OptimizedCustomerValidator();
        _validationService = new ValidationService(CreateServiceProvider());
    }
    
    public void BenchmarkDirectValidation()
    {
        var stopwatch = Stopwatch.StartNew();
        
        for (int i = 0; i < 10000; i++)
        {
            var result = _validator.Validate(_customer);
        }
        
        stopwatch.Stop();
        Console.WriteLine($"Direct validation (10,000 iterations): {stopwatch.ElapsedMilliseconds}ms");
    }
    
    public async Task BenchmarkServiceValidation()
    {
        var stopwatch = Stopwatch.StartNew();
        
        for (int i = 0; i < 10000; i++)
        {
            var result = await _validationService.ValidateAsync(_customer);
        }
        
        stopwatch.Stop();
        Console.WriteLine($"Service validation (10,000 iterations): {stopwatch.ElapsedMilliseconds}ms");
    }
    
    private IServiceProvider CreateServiceProvider()
    {
        var services = new ServiceCollection();
        services.AddScoped<IValidator<Customer>, OptimizedCustomerValidator>();
        services.AddScoped<IValidationService, ValidationService>();
        return services.BuildServiceProvider();
    }
}

// Usage example
class Program
{
    static async Task Main()
    {
        // Performance test
        var benchmark = new ValidationBenchmark();
        benchmark.BenchmarkDirectValidation();
        await benchmark.BenchmarkServiceValidation();
        
        // Type-safe validation usage example
        var services = new ServiceCollection();
        services.AddScoped<IValidator<Customer>, OptimizedCustomerValidator>();
        services.AddScoped<IValidationService, ValidationService>();
        var serviceProvider = services.BuildServiceProvider();
        
        var validationService = serviceProvider.GetService<IValidationService>();
        var customer = new Customer { Surname = "", Forename = "John" };
        
        var result = await validationService.ValidateAsync(customer);
        
        if (!result.IsValid)
        {
            Console.WriteLine("Validation errors:");
            foreach (var error in result.Errors)
            {
                Console.WriteLine($"- {error.PropertyName}: {error.ErrorMessage}");
            }
        }
        
        // Check errors for specific property
        if (result.HasErrorsFor(x => x.Surname))
        {
            var surnameErrors = result.GetErrorsFor(x => x.Surname);
            Console.WriteLine($"Surname error count: {surnameErrors.Count()}");
        }
    }
}