views:

836

answers:

3

I am trying to come up with a validation for a nullable property, like int?

Example

    [RangeValidator(0, RangeBoundaryType.Inclusive, 1, RangeBoundaryType.Inclusive)]
    int? Age { get; set; }

However if I set Age to null validation fails because it doesn't fall in the range, I know I need an [ValidatorComposition(CompositionType.Or)] as well, but what else should I use?

+2  A: 

You could add the IgnoreNulls attribute:

[IgnoreNulls()]
[RangeValidator(0, RangeBoundaryType.Inclusive, 1, RangeBoundaryType.Inclusive)]
int? Age { get; set; }
Steven Robbins
This works as long as the validation passes. If it fails, the resulting ValidationResults will contain an OrCompositeValidator with the RangeValidator as one of the nested ValidationResults, as pointed out by Paul. There is an alternative - see my answer below.
Philippe
+1  A: 

Yes but then if the RangeValidator causes a ValidationResult, then for some reason I end up wth two ValidationResults... one correctly for the range validation problem but then another cryptic one that says:

The value is not null and failed all its validation rules for key Age.

This is silly, I don't EVER want the IgnoreNulls validator to cause a validation result! Its really there to modify the others, isn't it. Add this to the lack of real Validation inheritance and functionality when using polymorphism, and so so many other things, and there are so many "small" problems with the VAB attributes that I find it unusable for anything beyond the trivial.

Paul
A: 

While the IgnoreNulls attribute can be added, it leads to convoluted ValidationResults when validation results are returned. This is because the validation block implicitly wraps the validators into an OrCompositeValidator - the property can be null OR it can be an integer in the range specified.

When the validation fails, the top-level validation result is for the OrCompositeValidator. To get the actual RangeValidator validation result, you now need to drill down into the NestedValidationResults property on the ValidationResult object.

This seems like a lot of work to process validation result messages, so it seemed to me that there had to be a better way.

Here is what I did.

  1. I created a class called IgnoreNullStringLengthValidator that inherits from the StringLengthValidator (here you would inherit the RangeValidator).
  2. Create all the constructors needed to support the base constructors.
  3. Override the DoValidate method and check for a null value - here you would write: if (!objectToValidate.HasValue) return;
  4. Make sure your next line of code calls base.DoValidate(...).
  5. Created an attribute class called IgnoreNullStringLengthValidatorAttribute that returns the new IgnoreNullStringLengthValidator. Here, you would create an IgnoreNullRangeValidatorAttribute class.

The resulting validation result is much more in line with what you'd expect because it does not nest your validators implicitly.

Philippe