public class User
{
public int Id { get; set; }
public string Login { get; set; }
public string Password { get; set; }
}
public class Person : User
{
public string FirstName { get; set; }
public string Surname { get; set; }
}
Walidator dla klasy User może wyglądać następująco:
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(p => p.Id)
.GreaterThanOrEqualTo(0);
RuleFor(p => p.Login)
.NotEmpty()
.Length(5, 20);
RuleFor(p => p.Password)
.NotEmpty()
.Length(6, 30);
}
}
Co w takim razie zrobić z walidatorem klasy Person? Możemy go stworzyć jako AbstractValidator<Person>, ale od nowa trzeba będzie definiować reguły dla odziedziczonych pól, czyli niefajnie. Pozornie lepszym wyjściem jest dziedziczenie po UserValidator. Reguły będą poprawnie działać, ale stracimy interfejs fluent. Dlaczego? Ponieważ w drzewie dziedziczenia PersonValidator będzie uznany jak AbstractValidator<User>. Stąd metoda RuleFor będzie potrafiła jedynie odwoływać się do części bazowej klasy Person. Tak i tak niedobrze.
Dlatego przyjrzyjmy się bliżej w jaki sposób działa sama biblioteka. Interesuje nas głównie AbstactValidator. Okazuje się, że żądania walidacji opierają się ostatecznie na wywołaniu metody Validate(ValidationContext<T> context)(#70). Z źródła wynika, że metoda ta sprawdza każdą regułę używając kontekstu walidacji, a następnie łączy wyniki w jedną całość. I tu pojawia się pomysł... A może by tak do zbioru wynikowego dodać rezultat pracy walidatora bazowego. Tak powstał InheritanceValidator<TBase, TThis> przeciążający metodę Validate.
public abstract class InheritanceValidator<TBase, TThis> : AbstractValidator<TThis>
where TThis : TBase
{
private AbstractValidator<TBase> _baseValidator;
public InheritanceValidator(AbstractValidator<TBase> baseValidator)
{
_baseValidator = baseValidator;
}
public override ValidationResult Validate(ValidationContext<TThis> context)
{
//stworzenie kontekstu dla wywołania reguł walidatora bazowego
ValidationContext<TBase> baseValidationContext = new ValidationContext<TBase>
(context.InstanceToValidate, context.PropertyChain, context.Selector);
//wywołanie reguł bazowego walidatora
IEnumerable<ValidationFailure> baseFailures = _baseValidator.SelectMany((rule) => {
return rule.Validate(baseValidationContext);
});
//wywołanie reguł walidatora klasy dziedziczącej i połączenie ich z wynikami bazowymi
IEnumerable<ValidationFailure> failures = this.SelectMany((rule) => {
return rule.Validate(context);
}).Concat(baseFailures);
//stworzenie klasy reprezentującej ostateczny wynik
return new ValidationResult(failures.ToList<ValidationFailure>());
}
}
W ten sposób jeżeli implementując PersonValidator wykorzystany zostanie InheritanceValidator, to podczas walidacji sprawdzone zostaną wszystkie dostępne reguły zarówno w walidatorze podstawowym jak i bazowym.
public class PersonValidator : InheritanceValidator<User, Person>
{
public PersonValidator() :
base(new UserValidator())
{
RuleFor(p => p.FirstName)
.NotEmpty();
RuleFor(p => p.Surname)
.NotEmpty();
}
}
Gdzie użycie gotowego walidatora pozostaje proste tak jak zawsze było:
Person p = new Person(); PersonValidator validator = new PersonValidator(); ValidationResult result = validator.Validate(p);Na zakończenie dodam jeszcze jedno. Podany wyżej sposób sprawdza się i działa tak jak powinien. Istnieje jednak prawdopodobieństwo, że jest jakaś lepsza metoda o której najzwyczajniej w świecie nie wiem ;]
Dla zainteresowanych zamieszczam również przykładowy projekt wykorzystujący przedstawione rozwiązanie.

Brak komentarzy:
Prześlij komentarz