Integracja Fluent Validation z WPF

Fluent Validation z WPF

W poprzednim wpisie pokazałem, w jaki sposób można zintegrować Fluent Validation z aplikacją ASP.NET MVC. Dzisiaj przyszedł czas na Fluent Validation z WPF.

W WPF jest kilka sposobów na realizację walidacji. Najczęściej wykorzystuje się do tego interfejs IDataErrorInfo, który definiuje dwa elementy: właściwość Error zawierającą informacje o błędach oraz indekser, który zwraca błąd dla właściwości o nazwie przekazanej jako indeks. Interfejs ten można wykorzystać do integracji Fluent Validation z WPF.

W swoich aplikacjach walidację realizuję głównie na poziomie view modeli. Aplikacja na czas edycji kopiuje dane z modelu do właściwości view modelu. Nie edytuje danych bezpośrednio w modelu, ponieważ bardzo często zostałyby one rozpropagowane na całą aplikację, jeszcze przed ich zatwierdzeniem. Dlatego właśnie walidator będzie tworzony dla view modelu.

Implementacja

Implementacja IDataErrorInfo jest realizowana na poziomie bazowego view modelu i wygląda mniej więcej tak:

public virtual ValidationResult SelfValidate()
{
return new ValidationResult();
}
private string GetValidationError(string property = null)
{
var validationResult = SelfValidate();
if (validationResult == null || validationResult.IsValid)
{
IsValid = true;
return string.Empty;
}
IsValid = false;
if (property == null)
{
return string.Join(Environment.NewLine, validationResult.Errors.Select(e => e.ErrorMessage));
}
else
{
var results = validationResult.Errors.FirstOrDefault(e => e.PropertyName == property);
return results != null ? results.ErrorMessage : string.Empty;
}
}
public string Error
{
get
{
return GetValidationError();
}
}
public string this[string property]
{
get
{
return GetValidationError(property);
}
}
public bool IsValid { get; private set; }
protected bool Validate()
{
IsValid = SelfValidate().IsValid;
return IsValid;
}

Kluczem całej integracji jest SelfValidate, w której poszczególne view modele będą uruchamiać metodę Validate swoich walidatorów. Metoda ta jest wykorzystywana przez pozostałe elementy integracji.

Właściwość Error oraz indekser z IDataErrorInfo wykorzystują pomocniczą metodę GetValidationError, który korzysta z SelfValidate oraz odpowiednio formatuje zwracany string w zależności od tego, czy wiadomość ma być przekazana dla całego obiektu, czy tylko dla określonej właściwości. W obu przykładach zwrócenie pustego napisu oznacza, że obiekt lub właściwość waliduje się poprawnie.

W mojej implementacji dodatkowo dodaję właściwość IsValid, która przechowuje informację o ostatniej walidacji. Właściwość tę można wykorzystać na przykład w komendach, aby na jej podstawie określić, czy przycisk zapisu ma być aktywny, czy nie. We właściwości nie uruchamiam walidacji, ponieważ w normalnej sytuacji byłaby ona powielona. WPF po zmianie właściwości w interfejsie użytkownika będzie w pierwszej kolejności korzystał z indeksera, który wykona walidację, dlatego przy komendzie będziemy tylko odczytywać ostatni jej wynik.

Dodatkowo dodaję jeszcze metodę Validate, aby można było ręcznie z poziomu view modelu uruchomić walidację danych.

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?

Użycie Walidatora

Samo użycie takiego kodu jest dość proste i może wyglądać następująco:

public class MainWindowViewModel : ViewModel, IMainWindowViewModel
{
protected MainWindowViewModelValidator Validator { get; set; }
public MainWindowViewModel(IMainWindow view)
: base(view)
{
Validator = new MainWindowViewModelValidator();
}
private string _email;
public string Email
{
get { return _email; }
set
{
if (_email != value)
{
_email = value;
OnPropertyChanged(() => this.Email);
SaveCommand.RaiseCanExecuteChanged();
}
}
}
private DelegateCommand _saveCommand;
public DelegateCommand SaveCommand
{
get
{
if(_saveCommand == null)
{
_saveCommand = new DelegateCommand(Save, () => IsValid);
}
return _saveCommand;
}
}
public void Save()
{
}
public override ValidationResult SelfValidate()
{
return Validator.Validate(this);
}
}
<Window x:Class="FluentValidationWithWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FluentValidationWithWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}"
Name="Email"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Errors: " />
<TextBlock Text="{Binding ElementName=Email, Path=(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
<Button Content="Save"
Command="{Binding SaveCommand}"/>
</StackPanel>
</Window>
public class MainWindowViewModelValidator : AbstractValidator<MainWindowViewModel>
{
public MainWindowViewModelValidator()
{
RuleFor(u => u.Email)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty()
.EmailAddress();
}
}

W przykładzie view model zawiera dwa elementy: właściwość Email, którą będziemy walidować, oraz komendę SaveCommand (do komendy wykorzystałem DelegateCommand z biblioteki Prism). W seterze właściwości Email na końcu uruchamiamy metodę RaiseCanExecuteChanged komendy, która informuje ją, aby przeliczyć jeszcze raz informację o aktywacji (lub nie) komendy. Sama komenda sprawdza właściwość IsActive view modelu, aby określić, czy przycisk zapisu jest aktywny.

View model wykorzystuje walidator, na którym uruchamiana jest metoda Validate w metodzie SelfValidate. Walidator zawiera dwie reguły sprawdzające kaskadowo właściwość Email – czy jest pusty oraz czy ma poprawny format adresu e-mail.

Sam widok jest dość prosty i zawiera trzy elementy. TextBox, w którym użytkownik wpisuje email, TextBlock wyświetlający błędy oraz przycisk.

Kluczowym elementem TextBoxa jest binding właściwości Text, a konkretnie dwie informacje. ValidatesOnDataErrors ustawione na true informuje, że WPF ma skorzystać z interfejsu IDataErrorInfo do walidacji właściwości Email. UpdateSourceTrigger ustawione na PropertyChanged informuje, że wartość ma być przekazywana do view modelu po każdej zmianie (domyślnie jest to robione po starcie focusa w TextBox), dzięki czemu walidacja będzie wykonywana po wpisaniu lub usunięciu każdej literki.

W bindingu można pomyśleć jeszcze o właściwości Delay (liczba milisekund) – ustawienie jej na wartość na przykład 500 spowoduje, że binding będzie przesyłał nową wartość właściwości, gdy odstęp między jej zmianami będzie dłuższy niż pół sekundy. Dzięki temu możemy zoptymalizować liczbę wykonywanych walidacji i w praktyce walidować dane w momencie, gdy użytkownik przestanie je wpisywać. Ma to szczególnie duże znaczenie, gdy walidacja jest wykonywana po stronie serwera.

Dwa ostatnie elementy widoku to TextBlock, który wyświetla błędy dla właściwości Email, oraz przycisk zapisu, który wykorzystuje komendę SaveCommand z view modelu. W przypadku przycisku ładnie widać, że aktywuje się lub dezaktywuje w zależności od tego, czy dane są poprawne, czy nie.

Podsumowanie

Jak widać, w ten prosty sposób można zintegrować aplikację WPF z Fluent Validation. Nie jest on idealny (niestety nie zadziała on, gdy będziemy chcieli wykonywać asynchroniczne walidację po stronie serwera – pokażę to w następnym wpisie), ale w większości zastosowań sprawdzi się bardzo dobrze.

Cały działający przykład jest dostępny na githubie.

1 thought on “Integracja Fluent Validation z WPF

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.