Simple.Validation v0.2.3

A new release of Simple.Validation is available on NuGet.

The changes include:

While Simple.Data is not intended to focus on property-level validation so much that more complex validation scenarios are difficult, it must be recognized that property-level validation accounts for a large part of validation scenarios. In keeping with the goal of simplicity, Simple.Validation provides some mechanisms for wiring up property-level validations quickly. The Fluent Properties API provides factory methods for creating validators for common property types. When used in conjunction with the CompositeValidator<T> it is a trivial task to get simple property-level validation implemented quickly.

This code is from the Personnel.Sample project included in the source code.

    public class SaveAddressValidator : CompositeValidator<Address>
    {
        public override bool AppliesTo(string rulesSet)
        {
            return rulesSet == RulesSets.Crud.Save;
        }

        protected override IEnumerable<IValidator<Address>> GetInternalValidators()
        {
            yield return Properties<Address>.
                For(a => a.Line1)
                .Length(0, 50)
                .Required()
                .Message("Address Line 1 is required.");

            yield return Properties<Address>
                .For(a => a.Line2)
                .Length(0, 50)
                .NotRequired()
                .Message("Line 2 must be between 0 and 50 characters in length.");

            yield return Properties<Address>
                .For(a => a.Line3)
                .Length(0, 50)
                .NotRequired()
                .Message("Line 3 must be between 0 and 50 characters in length.");

            yield return Properties<Address>
                .For(a => a.PostalCode)
                .Length(0, 20)
                .NotRequired()
                .Message("Postal Code must be between 0 and 20 characters in length.");

            yield return Properties<Address>
                .For(a => a.Country)
                .Length(0, 3)
                .NotRequired()
                .Message("Country must be between 0 and 3 characters in length.");

            yield return Properties<Address>
                .For(a => a.StateOrProvince)
                .Length(0, 50)
                .NotRequired()
                .Message("StateOrProvince must be between 0 and 50 characters in length.");

        } 
    }

 

In this sample, each call to Properties<T>.For() returns an instance of StringPropertyValidator. CompositeValidator will accumulate the results from each of the property-level validators and return them all as a single result set. What about non-string properties?

    public class SaveEmployeeValidator : CompositeValidator<Employee>
    {
        public override bool AppliesTo(string rulesSet)
        {
            return rulesSet == RulesSets.Crud.Save;
        }

        protected override IEnumerable<IValidator<Employee>> GetInternalValidators()
        {
            yield return Properties<Employee>
                .For(e => e.FirstName)
                .Length(3, 50)
                .Required()
                .IgnoreWhiteSpace();

            yield return Properties<Employee>
                .For(e => e.LastName)
                .Length(3, 50)
                .Required()
                .IgnoreWhiteSpace()
                ;

            yield return Properties<Employee>
                .For(e => e.Age)
                .MinValue(18)
                .MaxValue(65)
                ;

            yield return Properties<Employee>
                .For(e => e.Address)
                .Required()
                .Cascade("Save")
                ;

            yield return Properties<Employee>
                .For(e => e.ContactInfo)
                .Required()
                .Count(1)
                .Unique<ContactInfo>(c => c.Type)
                .Cascade("Save");

        }
    }

Employee.Age is of type Int32. Properties<T>.For() any property that is convertible to IComparable will return an instance of RangePropertyValidator. If the property is a reference type, it will return an instance of ReferencePropertyValidator, and if it’s a collection type it will return an instance of EnumerablePropertyValidator.

ReferencePropertyValidator and EnumerablePropertyValidator are similar in that they support Cascading validation onto the reference or collection property itself. This means that Valdiator.Validate<T> will be called for the value of the reference property, or for each element of the collection property. The results will be accumulated into the overall validator results.

When performing cascade validation, there is some question about the type that should be passed to Validator.Validate<T>. Should it be the declared property type (or declared type of the elements of the collection), or should Simple.Validation use the actual type of the property value (or collection element). By default, Cascade() uses the actual type of the property value or collection element, but you can specify the usage of a base type or interface by using the Cascade<T> overload.

In the Personnel.Sample project EmailAddress inherits ContactInfo. There is an EmailAddressValidator that tests the email address against a regular expression. If an instance of EmailAddress is added to the Employee.ContactInfo collection, should it be validated using the EmailAddressValidator or the ContactInfoValidator? Calling the non-generic Cascade() method will cause it to be validated using EmailAddressValdiator. Calling Cascade<ContactInfo>() will cause it to be validated only by the SaveContactInfoValidator.

Leave a Reply