Dynamic Registration of FluentValidation in .NET Minimal API

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

  • Reduced Boilerplate Code: With dynamic registration, developers no longer need to manually register each validator class. This reduces boilerplate code and makes the registration process more concise and maintainable.
  • Improved Scalability: As your application grows and evolves, dynamic registration accommodates new validator classes seamlessly. Adding or modifying validation logic becomes simpler and requires minimal changes to the registration code.
  • Enhanced Flexibility: Dynamic registration enables developers to adhere to conventions or patterns for validator class naming and structure. This promotes consistency and simplifies maintenance across the codebase.

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
  • Scanning Assemblies: Utilize reflection to scan assemblies at runtime and identify classes that implement the IValidator<T> interface. This involves iterating through types, filtering out validator classes, and extracting generic type arguments.
  • Registering Validators: Once validator classes are identified, register them with the dependency injection container using the appropriate service lifetime scope (e.g., scoped, transient). Map each validator interface (IValidator<T>) to its corresponding concrete implementation.
  • Integration with Startup Logic: Integrate the dynamic registration logic into the startup/configuration code of your .NET 6 Minimal API project. This typically involves invoking the scanning mechanism during application startup and configuring the dependency injection container accordingly.

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

  • Fluent Validation: link
  • .NET 8.0 Minimal API: link

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.