2013-09-06 15 views
7

Używam FluentValidation do sprawdzania poprawności kolekcji wewnątrz obiektu, porównując element elementów kolekcji z elementem obiektu nadrzędnego.Przekaż element obiektu do konstruktora SetValidator FluentValidator

Celem wyników jest otrzymanie informacji o poprawności dla każdego uszkodzonego elementu w kolekcji, a nie tylko o niepowodzenie w zbiorze.

Mam zamówienie na oprogramowanie, zawierające listę elementów oprogramowania. Jeśli zamówienie dotyczy starego systemu, wybrane oprogramowanie może być tylko starszym oprogramowaniem, i na odwrót, system nie będący starszym może mieć tylko nie istniejące oprogramowanie.

Mój model:

public class SoftwareOrder 
{ 
    public bool IsLegacySystem; 
    public List<SoftwareItem> Software; 
    (...other fields...) 
} 
public class SoftwareItem 
{ 
    public bool Selected; 
    public bool IsLegacySoftware; 
    public int SoftwareId; 
} 

Walidatory:

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder> 
{ 
    public SoftwareOrderValidator() 
    { 
    (..other rules..) 

    When(order => order.IsLegacySystem == true,() => 
    { 
     RuleForEach(order => order.SoftwareItem) 
      .SetValidator(new SoftwareItemValidator(true)); 
    }); 
    When(order => order.IsLegacySystem == false,() => 
    { 
     RuleForEach(order => order.SoftwareItem) 
      .SetValidator(new SoftwareItemValidator(false)); 
    }); 
    } 
} 
public class SoftwareItemValidator : AbstractValidator<SoftwareItem> 
{ 
    public SoftwareItemValidator(bool IsLegacySystem) 
    { 
    When(item => item.Selected,() => 
    { 
     RuleFor(item => item.IsLegacySoftware) 
      .Equal(IsLegacySystem).WithMessage("Software is incompatible with system"); 
    }); 
    } 
} 

Jak widać, mam zrealizować to przez posiadające Gdy dla każdego warunku. Działa, ale narusza DRY i nie jest praktyczne w użyciu w sytuacji, w której występują więcej niż dwa warunki.

bym idealnie chciałby mieć jeden RuleForEach, że może to zrobić, nie ma whens potrzebne, coś jak:

RuleForEach(order => order.SoftwareItem) 
    .SetValidator(new SoftwareItemValidator(order => order.IsLegacySystem)); 

Ale nie widzę żadnego sposobu, aby przejść IsLegacySystem do tego konstruktora.

Odpowiedz

14

postanowiłem dać to kolejny strzał, 2 lata później, po obejrzeniu ile poglądy na to pytanie bez odpowiedzi dostał. Mam dwie odpowiedzi.

Pierwsza odpowiedź to najlepsze rozwiązanie dla sytuacji opisanej w pytaniu.

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder> 
{ 
    public SoftwareOrderValidator() 
    { 
     RuleForEach(order => order.SoftwareItem) 
     .Must(BeCompatibleWithSystem) 
     .WithMessage("Software is incompatible with system"); 

    } 

    private bool BeCompatibleWithSystem(SoftwareOrder order, SoftwareItem item) 
    { 
     if (item.Selected) 
     return (order.IsLegacySystem == item.IsLegacySoftware); 
     else 
     return true; 
    } 
} 

Predicate Walidatory (a.k.a Must) mogą wziąć zarówno sprzeciw & nieruchomości jako argumenty. Pozwala to na bezpośrednie porównanie z IsLegacySystem lub dowolną inną właściwością obiektu nadrzędnego.

Prawdopodobnie nie powinieneś używać tej drugiej odpowiedzi. Jeśli uważasz, że musisz przekazać argumenty do konstruktora AbstractValidator, zachęcam Cię do ponownej oceny i znalezienia innego podejścia. Po tym ostrzeżeniu powiedziano, że jest to jeden sposób, aby to osiągnąć.

Zasadniczo należy użyć atrapowego Must(), aby umożliwić ustawienie zmiennej poza lambda, poza konstruktorem. Następnie możesz użyć tego, aby uzyskać tę wartość w konstruktorze drugiego walidatora.

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder> 
{ 
    private bool _isLegacySystem; 

    public SoftwareOrderValidator() 
    { 
     RuleFor(order => order.IsLegacySystem) 
     .Must(SetUpSoftwareItemValidatorConstructorArg); 

     RuleForEach(order => order.SoftwareItem) 
     .SetValidator(new SoftwareItemValidator(_isLegacySystem)); 

    } 

    private bool SetUpSoftwareItemValidatorConstructorArg(bool isLegacySystem) 
    { 
     _isLegacySystem = isLegacySystem; 
     return true; 
    } 
} 
public class SoftwareItemValidator : AbstractValidator<SoftwareItem> 
{ 
    public SoftwareItemValidator(bool IsLegacySystem) 
    { 
    When(item => item.Selected,() => 
    { 
     RuleFor(item => item.IsLegacySoftware) 
      .Equal(IsLegacySystem).WithMessage("Software is incompatible with system"); 
    }); 
    } 
} 
+1

tak słodko podchwytliwie – pirimoglu

1

Jak o tym podejściu, które wykorzystuje metodę Custom:

public class SoftwareOrder 
{ 
    public bool IsLegacySystem; 
    public List<SoftwareItem> Software; 
} 
public class SoftwareItem 
{ 
    public bool Selected; 
    public bool IsLegacySoftware; 
    public int SoftwareId; 
} 

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder> 
{ 
    public SoftwareOrderValidator() 
    { 
     Custom(order => 
     { 
      if (order.Software == null) 
       return null; 

      return order.Software.Any(item => order.IsLegacySystem != item.IsLegacySoftware) 
       ? new ValidationFailure("Software", "Software is incompatible with system") 
       : null; 
     }); 

     // Validations other than legacy check 
     RuleFor(order => order.Software).SetCollectionValidator(new SoftwareItemValidator()); 
    } 
} 

public class SoftwareItemValidator : AbstractValidator<SoftwareItem> 
{ 
    // Validation rules other than legacy check 
    public SoftwareItemValidator() 
    { 
    } 
} 
+0

To nie sprawdza poszczególnych pozycji, tylko kolekcji jako całości. Zaktualizowałem moje pytanie, aby wyjaśnić, że konieczne jest uzyskanie ValidationFailures dla każdego nieudanego elementu w kolekcji. – friggle

+0

Przepraszam również za brak odpowiedzi na tę odpowiedź przez 2 lata. Jeśli to jakieś pocieszenie, nigdy nie znalazłem rozwiązania. – friggle

5

wiem, że to jest stary pytanie i odpowiedź została już podana, ale natknąłem się na to pytanie dzisiaj i okazało się, że obecna wersja FluentValidation (używam 6.2.1.0) ma nowy Przeciążenie dla SetValidator, które pozwala przekazać Func jako parametr.

Tak, można zrobić:

RuleForEach(x => x.CollectionProperty) 
    // x below will referente the Parent class 
    .SetValidator(x => new CollectionItemValidator(x.ParentProperty); 

Mam nadzieję, że może to pomóc komuś tam.

+0

Dziękujemy za opublikowanie tej odpowiedzi, mimo że pytanie jest dość stare. Naprawdę mi to pomogło! – Mir

Powiązane problemy