Walidacja danych z FluentValidation

W prawie każdym projekcie jest potrzebna jakaś walidacja danych. Klasy służące do walidacji można napisać samemu, ale można również skorzystać z gotowych bibliotek. Najbardziej lubię FluentValidation.

FluentValidation

Jest to biblioteka, która ułatwia tworzenie „walidatorów” – klas zawierających reguły walidacji.

Aby ją dodać do naszego projektu, będziemy potrzebowali dołączyć następującą paczkę:

FluentValidation.AspNetCore

Tworzenie walidatora

Załóżmy, że mamy prosty obiekt:

public class SomeObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

Walidator dla tego obiektu może wyglądać następująco:

public class SomeObjectValidator : AbstractValidator<SomeObject>
{
    public SomeObjectValidator()
    {
        RuleFor(v => v.Name).NotEmpty().MaximumLength(10);
        RuleFor(v => v.Age).GreaterThanOrEqualTo(18);
        RuleFor(v => v.StartDate).LessThan(v => v.EndDate);
    }
}

Na początku tworzymy klasę, która dziedziczy po generycznej klasie AbstractValidator<>. Następnie w konstruktorze tej klasy dodajemy kolejne reguły walidacji korzystając z metody RuleFor. Możemy dodać jedną lub wiele reguł dla każdej właściwości. W naszym przypadku dodaliśmy następujące reguły:

  • Name nie może być puste oraz jego długość nie może być większa niż 10,
  • Age nie może być mniejsze niż 18,
  • StartDate musi być większe niż EndDate.

Użycie walidatora

Gdy walidator mamy już napisany, to wystarczy teraz stworzyć obiekt tego walidatora i wywołać na nim metodę Validate:

SomeObject someObject = new SomeObject();
SomeObjectValidator validator = new SomeObjectValidator();

ValidationResult result = validator.Validate(someObject);

Ta metoda zwraca obiekt typu ValidationResult, który ma dwie właściwości:

  • IsValid – flagę oznaczającą czy obiekt spełnia wszystkie reguły walidacji,
  • Errors – listę obiektów typu ValidationFailure zawierających szczegółowe informacje o tym, które reguły nie zostały spełnione i dlaczego.

Zamiast metody Validate możemy również użyć jej asynchronicznego odpowiednika ValidateAsync (zalecane wtedy, kiedy mamy jakieś asynchroniczne reguły walidacji). Jeśli natomiast nie chcemy zwracać wyniku walidacji, tylko rzucić wyjątek (w momencie gdy jakieś reguły nie są spełnione), to możemy użyć jedną z dwóch metod: ValidateAndThrow lub ValidateAndThrowAsync.

Rejestracja walidatora

Oczywiście naszych walidatorów nie musimy tworzyć ręcznie. Klasa AbstractValidator<> implementuje interfejs IValidator<>. W związku z tym walidatory można w dość prosty sposób zarejestrować w naszym kontenerze wstrzykiwania zależności (np. metoda ConfigureServices w klasie Startup):

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddTransient<IValidator<SomeObject>, SomeObjectValidator>();
    ...
}

Jeśli nie chcemy za każdym razem musieć pamiętać o tym, aby zarejestrować nasz walidator, to mamy dwa wyjścia. Możemy skorzystać z biblioteki Scrutor (pisałem o niej tutaj) albo możemy wykorzystać metodę dostarczoną przez FluentValidationAddFluentValidation. Możemy w niej wywołać metodę, która automatycznie zarejestruje wszystkie walidatory z jednego lub wielu assembly:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services
        .AddMvc()
        .AddFluentValidation(fv => 
            fv.RegisterValidatorsFromAssemblyContaining<SomeObjectValidator>()
        );
    ...
}

Automatyczna walidacja modelu

Dodatkową zaletą użycia metody AddFluentValidation jest to, że nasze walidatory będą uruchamiane automatycznie dla obiektów w publicznych metodach kontrolerów. To znaczy, że jeśli mielibyśmy w naszym kontrolerze taką metodę:

[HttpPost]
public async Task<IActionResult> AddSomeObjectAsync(
    [FromBody] SomeObject request)
{
   ...
}

To jeśli obiekt request nie spełnia reguł walidacji zdefiniowanych dla klasy SomeObject (w naszym przypadku reguł zdefiniowanych w klasie SomeObjectValidator), to automatycznie zostanie zwrócony komunikat BadRequest (HttpStatusCode = 400). Metoda AddSomeObjectAsync nie zostanie wykonana. Dodatkowo w odpowiedzi otrzymamy informacje o niespełnionych regułach walidacji, np.:

{  
  "Age": [
    "'Age' must be greater than or equal to '18'."
  ],
  "StartDate": [
    "'Start Date' must be less than '31/01/2020 07:17:56'."
  ]
}

Pominięcie automatycznie walidacji modelu

Może się zdarzyć tak, że ogólnie chcemy mieć automatyczną walidację, ale z jakiegoś powodu dla jednej metody nie chcemy sprawdzać reguł walidacji. W takiej sytuacji możemy skorzystać z atrybutu CustomizeValidator(Skip=true):

[HttpPost]
public async Task<IActionResult> AddSomeObjectAsync(
    [FromBody] [CustomizeValidator(Skip = true)] SomeObject request)
{
   ...
}

Po dodaniu tego atrybutu, nawet jeśli nasz obiekt nie spełnia reguł walidacji, to i tak metoda AddSomeObjectAsync się wykona.

Zaawansowane walidacje

W tym poście pokazałem tylko czym jest FluentValidation i jak go dodać do naszego projektu. W kolejnym opisałem zaawansowane zagadnienia, jakie dostarcza nam ta biblioteka (link).

1 myśl na “Walidacja danych z FluentValidation”

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz