@@ -33,7 +33,7 @@ namespace ServiceStack.FluentValidation {
3333 /// <typeparam name="T">The type of the object being validated</typeparam>
3434 public abstract partial class AbstractValidator < T > : IValidator < T > , IEnumerable < IValidationRule > {
3535 internal TrackingCollection < IValidationRule > Rules { get ; } = new TrackingCollection < IValidationRule > ( ) ;
36- private Func < CascadeMode > _cascadeMode = ( ) => ValidatorOptions . CascadeMode ;
36+ private Func < CascadeMode > _cascadeMode = ( ) => ValidatorOptions . Global . CascadeMode ;
3737
3838 /// <summary>
3939 /// Sets the cascade mode for all rules within this validator.
@@ -43,20 +43,12 @@ public CascadeMode CascadeMode {
4343 set => _cascadeMode = ( ) => value ;
4444 }
4545
46- ValidationResult IValidator . Validate ( object instance ) {
47- return ( ( IValidator ) this ) . Validate ( new ValidationContext ( instance ) ) ;
48- }
49-
50- Task < ValidationResult > IValidator . ValidateAsync ( object instance , CancellationToken cancellation ) {
51- return ( ( IValidator ) this ) . ValidateAsync ( new ValidationContext ( instance ) , cancellation ) ;
52- }
53-
54- ValidationResult IValidator . Validate ( ValidationContext context ) {
46+ ValidationResult IValidator . Validate ( IValidationContext context ) {
5547 context . Guard ( "Cannot pass null to Validate" , nameof ( context ) ) ;
5648 return Validate ( ValidationContext < T > . GetFromNonGenericContext ( context ) ) ;
5749 }
5850
59- Task < ValidationResult > IValidator . ValidateAsync ( ValidationContext context , CancellationToken cancellation ) {
51+ Task < ValidationResult > IValidator . ValidateAsync ( IValidationContext context , CancellationToken cancellation ) {
6052 context . Guard ( "Cannot pass null to Validate" , nameof ( context ) ) ;
6153 return ValidateAsync ( ValidationContext < T > . GetFromNonGenericContext ( context ) , cancellation ) ;
6254 }
@@ -67,7 +59,9 @@ Task<ValidationResult> IValidator.ValidateAsync(ValidationContext context, Cance
6759 /// <param name="instance">The object to validate</param>
6860 /// <returns>A ValidationResult object containing any validation failures</returns>
6961 public ValidationResult Validate ( T instance ) {
70- return Validate ( new ValidationContext < T > ( instance , new PropertyChain ( ) , ValidatorOptions . ValidatorSelectors . DefaultValidatorSelectorFactory ( ) ) ) ;
62+ return Validate ( new ValidationContext < T > ( instance , new PropertyChain ( ) , ValidatorOptions . Global . ValidatorSelectors . DefaultValidatorSelectorFactory ( ) ) {
63+ Request = Request
64+ } ) ;
7165 }
7266
7367 /// <summary>
@@ -77,7 +71,9 @@ public ValidationResult Validate(T instance) {
7771 /// <param name="cancellation">Cancellation token</param>
7872 /// <returns>A ValidationResult object containing any validation failures</returns>
7973 public Task < ValidationResult > ValidateAsync ( T instance , CancellationToken cancellation = new CancellationToken ( ) ) {
80- return ValidateAsync ( new ValidationContext < T > ( instance , new PropertyChain ( ) , ValidatorOptions . ValidatorSelectors . DefaultValidatorSelectorFactory ( ) ) , cancellation ) ;
74+ return ValidateAsync ( new ValidationContext < T > ( instance , new PropertyChain ( ) , ValidatorOptions . Global . ValidatorSelectors . DefaultValidatorSelectorFactory ( ) ) {
75+ Request = Request
76+ } , cancellation ) ;
8177 }
8278
8379 /// <summary>
@@ -98,14 +94,28 @@ public virtual ValidationResult Validate(ValidationContext<T> context) {
9894
9995 EnsureInstanceNotNull ( context . InstanceToValidate ) ;
10096
101- var failures = Rules . SelectMany ( x => x . Validate ( context ) ) ;
97+ foreach ( var rule in Rules ) {
98+ var failures = rule . Validate ( context ) ;
10299
103- foreach ( var validationFailure in failures . Where ( failure => failure != null ) ) {
104- result . Errors . Add ( validationFailure ) ;
100+ foreach ( var validationFailure in failures . Where ( failure => failure != null ) ) {
101+ result . Errors . Add ( validationFailure ) ;
102+ }
103+
104+ if ( CascadeMode == CascadeMode . Stop && result . Errors . Count > 0 ) {
105+ // Bail out if we're "failing-fast".
106+ // Check for > 0 rather than == 1 because a rule chain may have overridden the Stop behaviour to Continue
107+ // meaning that although the first rule failed, it actually generated 2 failures if there were 2 validators
108+ // in the chain.
109+ break ;
110+ }
105111 }
106112
107113 SetExecutedRulesets ( result , context ) ;
108114
115+ if ( ! result . IsValid && context . ThrowOnFailures ) {
116+ RaiseValidationException ( context , result ) ;
117+ }
118+
109119 return result ;
110120 }
111121
@@ -118,7 +128,6 @@ public virtual ValidationResult Validate(ValidationContext<T> context) {
118128 public async virtual Task < ValidationResult > ValidateAsync ( ValidationContext < T > context , CancellationToken cancellation = new CancellationToken ( ) ) {
119129 context . Guard ( "Cannot pass null to Validate" , nameof ( context ) ) ;
120130 context . RootContextData [ "__FV_IsAsyncExecution" ] = true ;
121- Init ( context ) ;
122131
123132 var result = new ValidationResult ( ) ;
124133
@@ -137,15 +146,27 @@ public virtual ValidationResult Validate(ValidationContext<T> context) {
137146 foreach ( var failure in failures . Where ( f => f != null ) ) {
138147 result . Errors . Add ( failure ) ;
139148 }
149+
150+ if ( CascadeMode == CascadeMode . Stop && result . Errors . Count > 0 ) {
151+ // Bail out if we're "failing-fast".
152+ // Check for > 0 rather than == 1 because a rule chain may have overridden the Stop behaviour to Continue
153+ // meaning that although the first rule failed, it actually generated 2 failures if there were 2 validators
154+ // in the chain.
155+ break ;
156+ }
140157 }
141158
142159 SetExecutedRulesets ( result , context ) ;
143160
161+ if ( ! result . IsValid && context . ThrowOnFailures ) {
162+ RaiseValidationException ( context , result ) ;
163+ }
164+
144165 return result ;
145166 }
146167
147168 private void SetExecutedRulesets ( ValidationResult result , ValidationContext < T > context ) {
148- var executed = context . RootContextData . GetOrAdd ( "_FV_RuleSetsExecuted" , ( ) => new HashSet < string > { "default" } ) ;
169+ var executed = context . RootContextData . GetOrAdd ( "_FV_RuleSetsExecuted" , ( ) => new HashSet < string > { RulesetValidatorSelector . DefaultRuleSetName } ) ;
149170 result . RuleSetsExecuted = executed . ToArray ( ) ;
150171 }
151172
@@ -191,14 +212,14 @@ public IRuleBuilderInitial<T, TProperty> RuleFor<TProperty>(Expression<Func<T, T
191212 /// <summary>
192213 /// Invokes a rule for each item in the collection
193214 /// </summary>
194- /// <typeparam name="TProperty ">Type of property</typeparam>
215+ /// <typeparam name="TElement ">Type of property</typeparam>
195216 /// <param name="expression">Expression representing the collection to validate</param>
196217 /// <returns>An IRuleBuilder instance on which validators can be defined</returns>
197- public IRuleBuilderInitialCollection < T , TProperty > RuleForEach < TProperty > ( Expression < Func < T , IEnumerable < TProperty > > > expression ) {
218+ public IRuleBuilderInitialCollection < T , TElement > RuleForEach < TElement > ( Expression < Func < T , IEnumerable < TElement > > > expression ) {
198219 expression . Guard ( "Cannot pass null to RuleForEach" , nameof ( expression ) ) ;
199- var rule = CollectionPropertyRule < TProperty > . Create ( expression , ( ) => CascadeMode ) ;
220+ var rule = CollectionPropertyRule < T , TElement > . Create ( expression , ( ) => CascadeMode ) ;
200221 AddRule ( rule ) ;
201- var ruleBuilder = new RuleBuilder < T , TProperty > ( rule , this ) ;
222+ var ruleBuilder = new RuleBuilder < T , TElement > ( rule , this ) ;
202223 return ruleBuilder ;
203224 }
204225
@@ -301,7 +322,7 @@ public IConditionBuilder UnlessAsync(Func<T, ValidationContext<T>, CancellationT
301322 /// </summary>
302323 public void Include ( IValidator < T > rulesToInclude ) {
303324 rulesToInclude . Guard ( "Cannot pass null to Include" , nameof ( rulesToInclude ) ) ;
304- var rule = IncludeRule . Create < T > ( rulesToInclude , ( ) => CascadeMode ) ;
325+ var rule = IncludeRule < T > . Create ( rulesToInclude , ( ) => CascadeMode ) ;
305326 AddRule ( rule ) ;
306327 }
307328
@@ -310,7 +331,7 @@ public void Include(IValidator<T> rulesToInclude) {
310331 /// </summary>
311332 public void Include < TValidator > ( Func < T , TValidator > rulesToInclude ) where TValidator : IValidator < T > {
312333 rulesToInclude . Guard ( "Cannot pass null to Include" , nameof ( rulesToInclude ) ) ;
313- var rule = IncludeRule . Create ( rulesToInclude , ( ) => CascadeMode ) ;
334+ var rule = IncludeRule < T > . Create ( rulesToInclude , ( ) => CascadeMode ) ;
314335 AddRule ( rule ) ;
315336 }
316337
@@ -347,5 +368,16 @@ protected virtual void EnsureInstanceNotNull(object instanceToValidate) {
347368 protected virtual bool PreValidate ( ValidationContext < T > context , ValidationResult result ) {
348369 return true ;
349370 }
371+
372+ /// <summary>
373+ /// Throws a ValidationException. This method will only be called if the validator has been configured
374+ /// to throw exceptions if validation fails. The default behaviour is not to throw an exception.
375+ /// </summary>
376+ /// <param name="context"></param>
377+ /// <param name="result"></param>
378+ /// <exception cref="ValidationException"></exception>
379+ protected virtual void RaiseValidationException ( ValidationContext < T > context , ValidationResult result ) {
380+ throw new ValidationException ( result . Errors ) ;
381+ }
350382 }
351383}
0 commit comments