Introduction

In the landscape of .NET development, validation is a critical aspect of ensuring data integrity and application robustness. FluentValidation is a widely adopted library that provides a clean and expressive way to define validation rules in .NET applications. In this blog post, we'll explore how to streamline the process of registering FluentValidation classes dynamically in .NET Minimal APIs. This approach reduces manual overhead and enhances the maintainability of validation logic in your applications.

The source code is available on GitHub

Understanding Dynamic Registration

Dynamic registration refers to the process of automatically discovering and registering components at runtime, based on predefined conventions or criteria. In the context of FluentValidation and .NET Minimal APIs, dynamic registration allows us to scan assemblies, identify validator classes, and register them with the dependency injection container without explicit manual intervention.

Benefits of Dynamic Registration with FluentValidation

Implementing Dynamic Registration

To implement dynamic registration of FluentValidation classes in .NET Minimal APIs, follow these steps.

The tools that we are leveraging here are.

  1. FluentValidation
  2. Microsoft.Extensions.DependencyInjection.Abstractions
  3. .NET 8.0
  4. Visual Studio 2022 Community Edition
  5. Web API

Code Snippet

//Person Domain Model

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

 }
//PersonValidation.cs
public class PersonValidation:AbstractValidator<Person>
{
    public PersonValidation()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required");
        RuleFor(x => x.Age).GreaterThan(0).WithMessage("Age must be greater than 18");
        RuleFor(x => x.Email).EmailAddress().WithMessage("Email is not valid");
    }
}
//Create Person Action
app.MapPost("Person",([FromBody] Person person, IValidator<Person> validator) =>
{
    var personValidation = validator.Validate(person);
    if (!personValidation.IsValid)
    {
        return Results.BadRequest(personValidation.ToDictionary());
    }
    return Results.Ok("Person created successfully");
})
    .WithName("CreatePerson")
    .WithOpenApi();
//TypeExtension.cs

/*
This extension method is designed to check whether a given type or any of its base types implement a generic type or generic interface specified by genericType. It handles both interface and inheritance hierarchies, recursively checking base types until a match is found or until there are no more base types to check.

*/

public static class TypeExtensions
{
    public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
    {
        var interfaceTypes = givenType.GetInterfaces();

        foreach (var it in interfaceTypes)
        {
            if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
                return true;
        }

        if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
            return true;

        var baseType = givenType.BaseType;
        if (baseType == null) return false;

        return IsAssignableToGenericType(baseType, genericType);
    }
}

//FluentValidationServiceCollections.cs
public static class FluentValidationServiceCollections
{
    public static void AddFluentValidationValidators(this IServiceCollection services)
    {
        var validatorTypes = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(t => !t.IsAbstract && t.IsAssignableToGenericType(typeof(AbstractValidator<>)))
            .ToList();

        foreach (var validatorType in validatorTypes)
        {
            var genericArg = validatorType.BaseType!.GetGenericArguments().FirstOrDefault();
            if (genericArg != null)
            {
                var serviceType = typeof(IValidator<>).MakeGenericType(genericArg);
                services.AddScoped(serviceType, validatorType);
            }
        }
    }
}
//register the fluent validation validators in Program.cs
builder.Services.AddFluentValidationValidators();

Additional Reference

Conclusion

Dynamic registration of FluentValidation classes in .NET Minimal APIs offers a powerful approach to streamline validation logic and enhance application maintainability. By automating the registration process based on conventions, developers can reduce manual overhead, improve scalability, and ensure consistency across their applications. Embracing dynamic registration empowers developers to focus on building robust and reliable validation logic without being burdened by repetitive registration tasks.