dotnet-cqrs/Svrnty.CQRS.FluentValidation/ServiceCollectionExtensions.cs

145 lines
6.9 KiB
C#

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<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(this IServiceCollection services)
where TValidator : class, IValidator<T>
{
services.AddTransient<IValidator<T>, TValidator>();
return services;
}
public static IServiceCollection AddCommand<TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(this IServiceCollection services)
where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand>
where TValidator : class, IValidator<TCommand>
{
return services.AddCommand<TCommand, TCommandHandler>()
.AddFluentValidator<TCommand, TValidator>();
}
public static IServiceCollection AddCommand<TCommand, TCommandResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(this IServiceCollection services)
where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand, TCommandResult>
where TValidator : class, IValidator<TCommand>
{
return services.AddCommand<TCommand, TCommandResult, TCommandHandler>()
.AddFluentValidator<TCommand, TValidator>();
}
public static IServiceCollection AddQuery<TQuery, TQueryResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TQueryHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(this IServiceCollection services)
where TQuery : class
where TQueryHandler : class, IQueryHandler<TQuery, TQueryResult>
where TValidator : class, IValidator<TQuery>
{
services.AddQuery<TQuery, TQueryResult, TQueryHandler>()
.AddFluentValidator<TQuery, TValidator>();
return services;
}
/// <summary>
/// Scans the specified assembly and registers all FluentValidation validators found.
/// Supports multiple validators per type (e.g., for composite validation scenarios).
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="assembly">The assembly to scan for validators</param>
/// <param name="filter">Optional filter to include/exclude specific validator types</param>
/// <param name="lifetime">Service lifetime for validators (default: Transient)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddValidatorsFromAssembly(
this IServiceCollection services,
Assembly assembly,
Func<Type, bool>? filter = null,
ServiceLifetime lifetime = ServiceLifetime.Transient)
{
return AddValidatorsFromAssembly(services, assembly, filter, null, lifetime);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="assembly">The assembly to scan for validators</param>
/// <param name="filter">Optional additional filter to include/exclude specific validator types</param>
/// <param name="lifetime">Service lifetime for validators (default: Transient)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddCommandValidatorsFromAssembly(
this IServiceCollection services,
Assembly assembly,
Func<Type, bool>? filter = null,
ServiceLifetime lifetime = ServiceLifetime.Transient)
{
return AddValidatorsFromAssembly(
services,
assembly,
filter,
validatedType => validatedType.Name.EndsWith("Command", StringComparison.Ordinal),
lifetime);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="assembly">The assembly to scan for validators</param>
/// <param name="filter">Optional additional filter to include/exclude specific validator types</param>
/// <param name="lifetime">Service lifetime for validators (default: Transient)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddQueryValidatorsFromAssembly(
this IServiceCollection services,
Assembly assembly,
Func<Type, bool>? 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<Type, bool>? validatorTypeFilter,
Func<Type, bool>? 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;
}
}