Extending IValidatorBuilder

Validatum is easy to extend by creating your own extension methods targeting the IValidatorBuilder<T> interface.


Writing an extension method

There are some golden rules to follow to ensure you build high quality extension methods.

  • Use the extension method name as the rule name for the broken rule.
  • Provide a For function overload method that has a selector expression.
  • Provide optional key and message parameters.
  • Provide a default message to the broken rule.
  • Don't use other validation functions in your validation function (don't chain functions). Conditional When and WhenNot and the For function can be used.
  • DON'T call the Build() function.

It's also important to understand the extension method is executed in two phases, the building phase and the validation phase. See comments in example below.

Example

using System;
using System.Linq.Expressions;
using Validatum; // namespace of IValidatorBuilder<T>

namespace MyProject
{
    public static class ValidatorBuilderExtensions
    {
        /// <summary>
        /// Adds a validator to ensure the integer value is not thirteen.
        /// </summary>
        public static IValidatorBuilder<int> NotThirteen(this IValidatorBuilder<int> builder,
            string key = null,
            string message = null)
        {
            if (builder is null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            // ============================================================ //
            // Anything outside the 'builder' is the building phase.        //
            // Code here will execute when the Build() method is called on  //
            // the builder.                                                 //
            // ============================================================ //

            return builder
                .When(
                    ctx => ctx.Value == 13,
                    ctx => 
                    {
                        // ============================================================ //
                        // Anything inside the 'builder' is the validation phase.       //
                        // Code here will execute when the Validate() method is called  //
                        // on the validator.                                            //
                        // ============================================================ //

                        // use the name of the extension method as broken rule name
                        // key and message passed to broken rule with default message if null
                        ctx.AddBrokenRule(
                            nameof(NotThirteen), 
                            key, 
                            message ?? "Value cannot equal 13.");
                    });
        }

        /// <summary>
        /// Adds a validator to ensure the integer value is not thirteen 
        /// for the target of the selector expression.
        /// </summary>
        public static IValidatorBuilder<T> NotThirteen<T>(this IValidatorBuilder<T> builder, 
            Expression<Func<T, int>> selector, // make sure your expression targets the correct type
            string key = null,
            string message = null)
        {
            if (builder is null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (selector is null)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            // set the key to use the path of the selected property if a key has not been provided
            // e.g. with selector expression 't => t.Address.AddressLine1'
            //      key will be 'Address.AddressLine1'
            key = key ?? selector.GetPropertyPath();

            // use the For function to call your validation function
            return builder.For<T, int>(selector, p => p.NotThirteen(key, message));
        }
    }
}

Using the extension method

var validator = new ValidatorBuilder<int>()
    .GreaterThan(10)
    .NotThirteen()
    .LessThan(20)
    .Build();

var result = validator.Validate(13);
var rule = result.BrokenRules.First();

Console.WriteLine($"[{rule.Rule}] {rule.Key}: {rule.Message}");

// output
// [NotThirteen] Int32: Value cannot equal 13.

Using selector expression

var validator = new ValidatorBuilder<Person>()
    .GreaterThan(p => p.Age, 10)
    .NotThirteen(p => p.Age)
    .LessThan(p => p.Age, 20)
    .Build();

var result = validator.Validate(new Person { Name = "Steve", Age = 13 });
var rule = result.BrokenRules.First();

Console.WriteLine($"[{rule.Rule}] {rule.Key}: {rule.Message}");

// output
// [NotThirteen] Age: Value cannot equal 13.

Using For function.

var validator = new ValidatorBuilder<Person>()
    .GreaterThan(p => p.Age, 10)
    .For(p => p.Age, a => a.NotThirteen())
    .LessThan(p => p.Age, 20)
    .Build();

var result = validator.Validate(new Person { Name = "Steve", Age = 13 });
var rule = result.BrokenRules.First();

Console.WriteLine($"[{rule.Rule}] {rule.Key}: {rule.Message}");

// output
// [NotThirteen] Age: Value cannot equal 13.