Integracja FluentValidation z Minimal API w .NET

To ostatni artykuł z cyklu poświęconego FluentValidation. Rozpoczniemy od przybliżenia dwóch podstawowych sposobów rejestrowania walidatorów w aplikacjach .NET za pomocą mechanizmu wstrzykiwania zależności. Omówione zostaną metody umożliwiające rejestrację dedykowanych walidatorów oraz wykorzystanie pakietu FluentValidation.DependencyInjectionExtensions, który znacznie upraszcza i automatyzuje proces rejestracji walidatorów.

Dodatkowo pokażemy, jak używać zarejestrowanych walidatorów w Minimal API, co pozwala na przejrzyste i efektywne zarządzanie walidacją danych wejściowych w nowoczesnych projektach. W artykule zostanie omówione również zarządzanie cyklem życia rejestrowanych walidatorów. Zapraszam do czytania.

Rejestrowanie określonego walidatora

Walidatory FluentValidation można z łatwością zintegrować z różnorodnymi bibliotekami odpowiedzialnymi za wstrzykiwanie zależności, w tym z popularną biblioteką Microsoft.Extensions.DependencyInjection. Proces integracji walidatora dla konkretnego typu modelu danych jest prosty i przejrzysty. Polega na zarejestrowaniu walidatora w dostawcy usług. Kluczowym krokiem jest zadeklarowanie walidatora jako typu IValidator<T>, gdzie T reprezentuje konkretny typ obiektu, który ma zostać poddany walidacji. Dzięki temu podejściu walidator staje się dostępny w całym systemie jako usługa, co pozwala na łatwe i elastyczne zarządzanie procesami walidacji danych.

builder.Services.AddTransient<IValidator<SomeModel>, SomeModelValidator>();

W powyższym przykładzie, SomeModelValidator jest jawnie zarejestrowany jako walidator dla typu SomeModel.

Użycie pakietu FluentValidation.DependencyInjectionExtensions znacznie upraszcza proces rejestracji walidatorów, oferując metody rozszerzające, które automatyzują skanowanie i rejestrowanie walidatorów w kontenerze usług. Aby skorzystać z tej metody, najpierw dodaj pakiet do projektu:

dotnet add package FluentValidation.DependencyInjectionExtensions

Po dodaniu pakietu dostępne stają się metody rozszerzające takie jak AddValidatorsFromAssemblyContaining, które pozwalają na zautomatyzowaną rejestrację wszystkich walidatorów zdefiniowanych w danym zestawie. Możesz wybrać spośród kilku przeciążeń tej metody, aby dostosować proces rejestracji do swoich potrzeb, włączając możliwość ustawienia zakresu życia walidatorów na Singleton, Scoped lub Transient.

// Overload 1 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining(typeof(StudentValidator));

// Overload 2 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>();

// Overload 3 - To Register all the validators
builder.Services.AddValidatorsFromAssemblyContaining<StudentValidator>(ServiceLifetime.Scoped);

Korzystając z tych metod, możesz znacznie upraszczać zarządzanie walidacjami w swoich aplikacjach, jednocześnie zachowując wysoką elastyczność i kontrolę nad procesem.

Czas życia walidatorów

Zasadniczo, zaleca się rejestrowanie walidatorów FluentValidation jako Transient, co jest najbezpieczniejszą i najprostszą opcją. Taki zakres zapewnia, że każda operacja walidacji korzysta z nowej, niezależnej instancji walidatora, eliminując tym samym ryzyko związane z dzieleniem stanu między operacje.

Rejestracja walidatora jako Singleton wymaga szczególnej ostrożności. W takim przypadku powinniśmy unikać wstrzykiwania do walidatora zależności o zakresie Transient lub Scoped, ponieważ może to prowadzić do skomplikowanych problemów związanych z zarządzaniem stanem i cyklem życia obiektów. Ogólnie, rejestrowanie walidatorów jako Singleton nie jest zalecane, chyba że mamy doświadczenie w korzystaniu ze wstrzykiwania zależności i wiemy, jak rozwiązywać problemy wynikające z posiadania zależności o różnych zakresach przez obiekty o zakresie Singleton.

Podsumowując, choć istnieje możliwość ustawienia różnych zakresów życia dla walidatorów, najczęściej sugerowanym i najbezpieczniejszym podejściem jest wykorzystanie zakresu Transient, szczególnie jeśli chcemy uniknąć potencjalnych pułapek związanych z zarządzaniem zależnościami.

Użycie FluentValidation w Minimal API

Po zarejestrowaniu walidatorów możemy je łatwo używać bezpośrednio w endpointach minimal API. Poniżej znajduje się przykładowa implementacja, która pokazuje, jak wykorzystać zarejestrowany walidator w minimal API. Najpierw zdefiniujmy model User oraz walidator UserValidator:

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.FirstName).NotEmpty().WithMessage("First name is required");
        RuleFor(x => x.LastName).NotEmpty().WithMessage("Last name is required");
        RuleFor(x => x.Email).EmailAddress().WithMessage("A valid email is required");
        RuleFor(x => x.Age).InclusiveBetween(18, 60).WithMessage("Age must be between 18 and 60");
    }
}

Dodajmy endpoint do minimal API, który wykorzystuje zarejestrowany walidator:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddValidatorsFromAssemblyContaining<UserValidator>();

var app = builder.Build();

app.MapPost("/users", async (IValidator<User> validator, User user) => 
{
    ValidationResult validationResult = await validator.ValidateAsync(user);

    if (!validationResult.IsValid) 
    {
        return Results.ValidationProblem(validationResult.ToDictionary());
    }

    // ...
});

app.Run();

Powyższy kod pokazuje, jak używać FluentValidation w minimal API. Walidacja odbywa się bezpośrednio w endpointach API, co pozwala na przejrzyste i łatwe zarządzanie walidacją danych wejściowych. W razie niepowodzenia walidacji, API zwraca odpowiednią odpowiedź z błędami walidacji.

Podsumowanie

W artykule przedstawiono metody integracji walidatorów FluentValidation z aplikacjami .NET, wykorzystując mechanizm wstrzykiwania zależności. Zostały omówione praktyczne aspekty tej integracji, podkreślając, jak ułatwia ona zarządzanie walidacjami w projekcie. Zostało zaznaczone, jak kluczowe jest rejestrowanie dedykowanych walidatorów oraz jakie korzyści płyną z użycia pakietu FluentValidation.DependencyInjectionExtensions, który automatyzuje i upraszcza proces integracji.

W artykule podkreślono także wagę odpowiedniego zarządzania czasem życia walidatorów, rekomendując zakres Transient dla zapewnienia maksymalnej niezawodności i minimalizacji ryzyka błędów związanych ze stanem współdzielonym. Zostały przedstawione potencjalne pułapki związane z użyciem zakresu Singleton oraz konieczność unikania mieszania zakresów życia zależności.

Dodatkowo omówiono szczegóły integracji FluentValidation z Minimal API, pokazując, jak używać zarejestrowanych walidatorów bezpośrednio w endpointach. Przykłady kodu ilustrują, jak przeprowadzać walidację danych wejściowych w minimal API oraz jak odpowiednio reagować na błędy walidacji, zwracając odpowiednie odpowiedzi HTTP.

Podsumowując, dzięki dogłębnemu zrozumieniu omówionych koncepcji i skutecznemu wykorzystaniu dostępnych narzędzi, można efektywnie wdrażać walidatory FluentValidation, co przekłada się na solidność, spójność i łatwość w zarządzaniu procesami walidacji w aplikacjach .NET Core, w tym również w nowoczesnych projektach opartych na minimal API.