Fluent Validation – walidacja danych w .NET

Wprowadzenie

Dzisiaj chciałbym przedstawić bibliotekę, którą wykorzystuje do walidacji danych. Biblioteka (Fluent Validation) jest tak fajna, że wdrożyłem ją w różnego rodzaju projekty – zaczynając od aplikacji webowych, po aplikacje desktop tworzone w WPF.

W Fluent Validation walidację jakiegoś obiektu zamykamy w dedykowanej klasie walidatora, dzięki czemu ładnie się komponuje między innymi z regułami SOLID. W walidatorze za pomocą składni fluent w konstruktorze określamy reguły walidacyjne dla poszczególnych właściwości. Sama biblioteka co do określenia reguł daje duże możliwości, które w większości wystarczają (ale można też definiować reguły samemu). Najlepiej zobaczyć to wszystko na przykładzie.

Fluent Validation – przykład

Poniżej znajduje się klasa użytkownika, która umożliwi sprawdzenie podstawowych funkcjonalności, jakie udostępnia Fluent Validation.

public class User
{
public User()
{
Address = new Address();
}
[Display(Name = "Nazwa użytkownika")]
public string Name { get; set; }
public string Email { get; set; }
public bool CreateInvoice { get; set; }
public string Nip { get; set; }
public Address Address { get; set; }
public List<Order> Orders { get; set; }
}
view raw User.cs hosted with ❤ by GitHub

Sam walidator dla klasy User może wyglądać w następujący sposób:

public class UserValidator : AbstractValidator<User>
{
public UserValidator(IUserLogic userLogic)
{
RuleFor(u => u.Name)
.NotEmpty();
RuleFor(u => u.Email)
.NotEmpty()
.WithMessage(UserResources.EmailIsRequiredError);
RuleFor(u => u.Email)
.EmailAddress()
.WithMessage("Email ma niepoprawny format");
When(u => u.CreateInvoice, () =>
{
RuleFor(u => u.Nip)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty()
.WithMessage("Nip jest wymagany")
.Must(nip => IsNipValid(nip))
.WithMessage("Nip ma niepoprawny format") ;
});
RuleFor(u => u.Email)
.Must(email => userLogic.Exist(email))
.WithMessage(u => string.Format("Użytkownik o adresie {0} już istnieje.", u.Email));
RuleFor(u => u.Address)
.SetValidator(new AddressValidator());
RuleFor(u => u.Orders)
.SetCollectionValidator(new OrderValidator());
}
private bool IsNipValid(string nip)
{
if (nip == null)
{
return false;
}
if (nip.Length != 10)
{
return false;
}
return true;
}
}

Jak widać w kodzie, utworzenie nowego walidatora jest bardzo proste – wystarczy tylko utworzyć nową klasę i odziedziczyć po klasie AbstractValidator, która jest klasą generyczną, w której parametr określa, jaki typ będzie walidowany przez walidator. Następnie w konstruktorze za pomocą metody RuleFor określamy reguły dla poszczególnych właściwości.

Reguły walidacyjne

Po wywołaniu metody RuleFor za pomocą innych metod (np. NotEmpty, NotNull, EmailAddress) określamy regułę, jaka ma być spełniona (lista wszystkich wbudowanych reguł: https://github.com/JeremySkinner/FluentValidation/wiki/c.-Built-In-Validators – możemy również tworzyć własne reguły). Po regule możemy określić wiadomość, jaka ma być wyświetlona, gdy wartość nie spełnia danej reguły. Robimy to za pomocą metody WithMessage, które może przyjmować różne warianty – na przykład przyjmować gotową wiadomość w stringu czy właściwości z pliku resource.

Omawiając dostępne reguły, warto wspomnieć o jednej z najciekawszych, czyli metodzie Must. Metoda ta jest o tyle fajna, że umożliwia dodanie do walidacji dowolnej reguły. Można na przykład wywołać jakąś lokalną metodę, która ma bardziej rozbudowaną walidację, tak jak to jest w przykładzie z walidacją NIP-u (tutaj chciałbym podkreślić, że walidacja NIP-u w przykładzie nie jest poprawną walidacją numeru NIP). Metodę Must można też wykorzystywać do uruchomienia jakiejś metody z obiektu, który jest wstrzyknięty do walidatora – na przykład możemy wstrzyknąć logikę biznesową i sprawdzić, czy adres email jest unikalny w aplikacji.

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Walidacja warunkowa

Fluent Validation wspiera również walidację warunkową, jak to zrobiono powyżej na przykładzie walidacji NIP-u. Za pomocą metody When można określić warunek, który musi być spełniony, aby reguły były wykorzystywane przez walidator. W przykładzie NIP jest walidowany w momencie, gdy flaga CreateInvoice jest ustawiona na true, czyli gdy dany użytkownik chce, aby aplikacja wystawiała mu faktury.

Metody When można użyć na dwa sposoby: po regule i wiadomości lub przy opakowaniu kilku reguł jednym When, tak jak na powyższym przykładzie. To drugie podejście przydaje się w sytuacji, gdy kilka reguł ma ten sam warunek. Wtedy nie musimy go powielać w kodzie i wystarczy, że w jednym miejscu go później zmienimy.

W momencie gdy mamy kilka reguł dla jednej właściwości, warto zastosować walidację kaskadową. Domyślnie Fluent Validation oblicza wszystkie reguły podczas walidacji, co niestety może spowodować, że użytkownik zobaczy kilka komunikatów naraz. Aby włączyć reguły kaskadowe, wystarczy wywołać metodę Cascade – tak jak w regule dla walidacji numeru NIP.

Fluent Validation wspiera również walidację powiązanych obiektów. Jak widać na przykładzie adresu oraz powiązanych zamówień, możemy walidować zarówno pojedyncze obiekty, jak i całe kolekcje, co jest bardzo przydatne. Wystarczy tylko dodać nowy walidator dla powiązanego typu obiektu i później skorzystać z metody SetValidator lub SetCollectionValidator.

Użycie walidatora

Na koniec warto jeszcze zobaczyć, w jaki sposób korzysta się z tak utworzonego walidatora. Dziś pokażę, jak to zrobić ręcznie w aplikacji konsolowej, natomiast w kolejnych wpisach zobaczymy, jak to zrobić w innych typach aplikacji.

static void Main(string[] args)
{
var user = new User()
{
CreateInvoice = true,
Nip = "123",
Email = "daniel@plawgo.pl",
Orders = new List<Order>()
{
new Order(){Product = "Laptop"},
new Order(){Cost = 14}
}
}
;
var validator = new UserValidator(new UserLogic());
var validationResult = validator.Validate(user);
if (validationResult.IsValid == false)
{
foreach (var error in validationResult.Errors)
{
Console.WriteLine("{0}: {1}", error.PropertyName, error.ErrorMessage);
}
}
}
view raw Program.cs hosted with ❤ by GitHub

Jak widać w powyższym kodzie, wystarczy utworzyć instancję walidatora. W normalnej aplikacji najlepiej wykorzystać do tego jakiś kontener wstrzykujący zależności, aby rozwiązywał automatycznie wszystkie zależności walidatora. Następnie uruchamiamy metodę Validate, przekazując obiekt do walidacji. W wyniku działania metody otrzymamy informację, czy obiekt jest poprawny (IsValid ma wartość true); w przeciwnym wypadku możemy za pomocą właściwości Errors przejść po liście błędów. Pojedynczy błąd zawiera informacje o nazwie właściwości, która ma błędną wartość, oraz komunikat błędu.

Wynikiem powyższego kodu jest:

Fluent Validation wynik walidacji

Fluent Validation – wynik walidacji

Sam kod przykładu można znaleźć na githubie.

1 thought on “Fluent Validation – walidacja danych w .NET

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.