using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentValidation; using Microsoft.Extensions.DependencyInjection; using Svrnty.CQRS.Abstractions; namespace Svrnty.CQRS.FluentValidation; public static class ServiceCollectionExtensions { private static IServiceCollection AddFluentValidator(this IServiceCollection services) where TValidator : class, IValidator { services.AddTransient, TValidator>(); return services; } public static IServiceCollection AddCommand(this IServiceCollection services) where TCommand : class where TCommandHandler : class, ICommandHandler where TValidator : class, IValidator { return services.AddCommand() .AddFluentValidator(); } public static IServiceCollection AddCommand(this IServiceCollection services) where TCommand : class where TCommandHandler : class, ICommandHandler where TValidator : class, IValidator { return services.AddCommand() .AddFluentValidator(); } public static IServiceCollection AddQuery(this IServiceCollection services) where TQuery : class where TQueryHandler : class, IQueryHandler where TValidator : class, IValidator { services.AddQuery() .AddFluentValidator(); return services; } /// /// Scans the specified assembly and registers all FluentValidation validators found. /// Supports multiple validators per type (e.g., for composite validation scenarios). /// /// The service collection /// The assembly to scan for validators /// Optional filter to include/exclude specific validator types /// Service lifetime for validators (default: Transient) /// The service collection for chaining public static IServiceCollection AddValidatorsFromAssembly( this IServiceCollection services, Assembly assembly, Func? filter = null, ServiceLifetime lifetime = ServiceLifetime.Transient) { return AddValidatorsFromAssembly(services, assembly, filter, null, lifetime); } /// /// Scans the specified assembly and registers FluentValidation validators for command types only. /// Filters validators to only include those validating types ending with "Command" or registered as command handlers. /// /// The service collection /// The assembly to scan for validators /// Optional additional filter to include/exclude specific validator types /// Service lifetime for validators (default: Transient) /// The service collection for chaining public static IServiceCollection AddCommandValidatorsFromAssembly( this IServiceCollection services, Assembly assembly, Func? filter = null, ServiceLifetime lifetime = ServiceLifetime.Transient) { return AddValidatorsFromAssembly( services, assembly, filter, validatedType => validatedType.Name.EndsWith("Command", StringComparison.Ordinal), lifetime); } /// /// Scans the specified assembly and registers FluentValidation validators for query types only. /// Filters validators to only include those validating types ending with "Query" or registered as query handlers. /// /// The service collection /// The assembly to scan for validators /// Optional additional filter to include/exclude specific validator types /// Service lifetime for validators (default: Transient) /// The service collection for chaining public static IServiceCollection AddQueryValidatorsFromAssembly( this IServiceCollection services, Assembly assembly, Func? filter = null, ServiceLifetime lifetime = ServiceLifetime.Transient) { return AddValidatorsFromAssembly( services, assembly, filter, validatedType => validatedType.Name.EndsWith("Query", StringComparison.Ordinal), lifetime); } private static IServiceCollection AddValidatorsFromAssembly( IServiceCollection services, Assembly assembly, Func? validatorTypeFilter, Func? validatedTypeFilter, ServiceLifetime lifetime) { var validatorInterfaces = assembly.GetTypes() .Where(type => type.IsClass && !type.IsAbstract && !type.IsGenericTypeDefinition) .Where(type => validatorTypeFilter == null || validatorTypeFilter(type)) .SelectMany(validatorType => validatorType.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IValidator<>)) .Select(interfaceType => new { ValidatorType = validatorType, InterfaceType = interfaceType })) .ToList(); foreach (var registration in validatorInterfaces) { var validatedType = registration.InterfaceType.GetGenericArguments()[0]; // Apply validated type filter (for command/query separation) if (validatedTypeFilter != null && !validatedTypeFilter(validatedType)) continue; var validatorType = registration.ValidatorType; // Register the validator var serviceType = typeof(IValidator<>).MakeGenericType(validatedType); services.Add(new ServiceDescriptor(serviceType, validatorType, lifetime)); } return services; } }