Extending CuttingEdge.Conditions

While CuttingEdge.Conditions covers most basic validation needs, developers will always have specific needs that CuttingEdge.Conditions (or any other library) doesn't cover. Though it is still possible to write those old fashion 'if (b) throw ex' statements, a more convenient solution is to write your own validation methods. This page gives some practical examples that show how to extend CuttingEdge.Conditions.

Before you start extending CuttingEdge.Conditions, there are two things you should pay attention to:

1. Place your extension class in your companies root namespace.
This way you'll never have to include that namespace. The only namespace you'll have to add is the CuttingEdge.Conditions namespace. After including this namespace, your own extension methods will show up automatically in the IntelliSense dropdown list.

2. Some members won't show up using IntelliSense.
Some members of the ConditionValidator<T> class are decorated with the EditorBrowsableAttribute, preventing them from showing up during IntelliSense. The most important hidden members are the ArgumentName and Value fields and the ThrowException method. Hiding those members makes a lot of sense, because they aren't used directly by users of the library. Showing them would clutter the API and make using the library more difficult. Hiding them however, makes extending the library harder and it is something you'll have to deal with while extending the library.

Adding new validations

The following code sample shows how to add an extra validation extension method, that you than can use just like the build-in validations:

C# example
using System.Collections.Generic;
using System.Linq;

using CuttingEdge.Conditions;

namespace YourCompanyRootNamespace
{
    public static class ConditionExtensions
    {
        [DebuggerStepThrough]
        public static ConditionValidator<T> ExistsIn<T>(
            this ConditionValidator<T> validator, IEnumerable<T> collection)
        {
            // Value, ArgumentName and ThrowException won't show up in the 
            // IntelliSense dropdown list, but you can use them.
            if (collection == null || !collection.Contains(validator.Value))
            {
                validator.ThrowException(validator.ArgumentName +
                    " should be in the supplied collection");
            }

            return validator;
        }
    }
}
VB.NET example
' TODO

The new 'ExistsIn' validation method can be used as follows:

C# example
void Method(int number)
{
    Condition.Requires(number, "number")
        .ExistsIn(new[] { 1, 2, 9, 10 });
}
VB.NET example
' TODO

Throwing different types of exceptions

Currently CuttingEdge.Conditions only supports validating preconditions using the Requires method overloads, and validating postconditions using the Ensures method overloads. A failing 'requires' results in an ArgumentException (or one of it's subtypes). A failing 'ensures' results in a PostconditionException. Throwing other types of exceptions is possible by extending CuttingEdge.Conditions.

The following example defines an 'Invariant' entry point method, that results in an InvalidOperationException on failure.

C# example
using System;
using CuttingEdge.Conditions;

namespace MyCompanyRootNamespace
{
    public static class Invariant
    {
        public static ConditionValidator<T> Requires<T>(T value)
        {
            return new InvariantValidator<T>("value", value);
        }

        public static ConditionValidator<T> Requires<T>(T value, 
            string argumentName)
        {
            return new InvariantValidator<T>(argumentName, value);
        }

        // Internal class that inherits from ConditionValidator<T>
        private sealed class InvariantValidator<T> : ConditionValidator<T>
        {
            public InvariantValidator(string argumentName, T value)
                : base(argumentName, value)
            {
            }

            protected override void ThrowExceptionCore(string condition,
                string additionalMessage, ConstraintViolationType type)
            {
                string exceptionMessage = string.Format(
                    CultureInfo.InvariantCulture, "Invariant '{0}' failed.", condition);

                if (!String.IsNullOrEmpty(additionalMessage))
                {
                    exceptionMessage += " " + additionalMessage;
                }

                // Optionally, the 'type' parameter can be used, but never 
                // throw an exception when the value of 'type' is unknown
                // or unvalid.
                throw new InvalidOperationException(exceptionMessage);
            }
        }
    }
}
VB.NET example
' TODO

The new 'Invariant' entry point method can be used as follows:

C# example
void Method()
{
    // Although IsGreaterThan is defined in the library,
    // you can use it with your new Invariant method.
    Invariant.Requires(this.Count, "Count")
        .IsGreaterThan(0);
}
VB.NET example
' TODO

Using CuttingEdge.Conditions for testing

With just a little coding, you can use CuttingEdge.Conditions with your favorite unit testing framework.

The following example defines an 'Assert' entry point method, that results in an call to the Assert.Fail method of the MSTest framework (but you can ofcourse use any framework you like).

C# example
using System;
using CuttingEdge.Conditions;

namespace MyCompanyRootNamespace
{
    public static class Test
    {
        public static ConditionValidator<T> Assert<T>(T value)
        {
            return new AssertionValidator<T>("value", value);
        }

        public static ConditionValidator<T> Assert<T>(T value, 
            string argumentName)
        {
            return new AssertionValidator<T>(argumentName, value);
        }

        // Internal class that inherits from ConditionValidator<T>
        private sealed class AssertionValidator<T> : ConditionValidator<T>
        {
            public AssertionValidator(string argumentName, T value)
                : base(argumentName, value)
            {
            }

            protected override void ThrowExceptionCore(string condition,
                string additionalMessage, ConstraintViolationType type)
            {			
                string exceptionMessage = string.Format(
                    CultureInfo.InvariantCulture, "Assertion '{0}' failed.", condition);

                if (!String.IsNullOrEmpty(additionalMessage))
                {
                    exceptionMessage += " " + additionalMessage;
                }

                // Make a call to your unit testing framework.
                Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Fail(
                    exceptionMessage);				
            }
        }
    }
}
VB.NET example
' TODO

The new 'Assert' entry point method can be used as follows:

C# example
[TestMethod]
public void Log_WithException_LogsASingleException()
{
    // Arrange
    int expectedNumberOfLoggedExceptions = 1;
    var exceptionToLog = new Exception("message");
    var logger = new FakeSqlLoggingProvider();

    // Act
    logger.Log(exceptionToLog);

    // Assert
    Test.Assert(logger.LoggedExceptions)
        .HasLength(expectedNumberOfLoggedExceptions);
}
VB.NET example
' TODO

Last edited Feb 15, 2011 at 8:27 PM by dot_NET_Junkie, version 8

Comments

No comments yet.