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