Option Validation

Option Validation

One of the Microsoft design patterns that I frequently use is called options. In case you’ve never heard of options, HERE is a link to Microsoft’s documentation about them. Options are nice, but, they could be better. For instance, I often want to validate my options and that’s code I have to roll together, on my own, each time I need it. To solve that problem, I added some code to the CODEGATOR CG.Options and CG.DataAnnotations NUGET packages. I thought I might write about that code today.

My validation extensions begin with the IValidatableObject interface, which is part of Microsoft’s System.ComponentModel.DataAnnotations namespace. That interface has a method on it that allows an object to validate itself. It’s the basis for how we all validate models in ASP.NET. I extended that mechanism with a couple of extension methods. The first is a method called Validate. Here is what that code looks like:

public static IEnumerable<ValidationResult> Validate(
    this IValidatableObject validatableObject
    )
{
    var results = validatableObject.Validate(
        new ValidationContext(validatableObject)
        );

    return results;
}

Here I pass in an IValidatableObject instance, and then I supply my own default ValidationContext object for the operation. Not an Earth shattering improvement, granted, but I did manage to make the validation operation a little more convenient.

The next extension method I wrote is called ThrowIfInvalid. As the name implies, this method throws an exception if the validation operation fails, for whatever reason. Here is what that code looks like:

public static void ThrowIfInvalid(
    this IValidatableObject validatableObject
    )
{
    var results = validatableObject.Validate(
        new ValidationContext(validatableObject)
        );

    if (results.Any())
    {
        var memberNames = string.Join(
            ",",
            results.Select(x => string.Join(",", x.MemberNames))
            );
        var errorMessages = string.Join(
            ",",
            results.Select(x => x.ErrorMessage)
            );

        throw new ValidationException(
            message: string.Format(
                Resources.VerifiableObject_Invalid,
                validatableObject.GetType().Name,
                errorMessages,
                memberNames
                )
            );
    }
}

Here I validate the object, then scan through the results looking for anything with an error message. If I find one, the validation operation failed, so I throw the exception.

The final extension method I added to the IValidatableObject interface is called IsValid. This method performs the validation operation and then returns either True, or False, depending on the result. Here is what that code looks like:

public static bool IsValid(
    this IValidatableObject validatableObject
    )
{
    var results = validatableObject.Validate(
        new ValidationContext(validatableObject)
        ).Any();
    return !results;
}

After I had the extension methods in place, I created a default implementation of the IValidatableObject, just in case I needed it later. Here is what that class looks like:

public abstract class ValidatableObject : IValidatableObject
{
    public virtual IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext
        )
    {
        var results = new List<ValidationResult>();

        var props = validationContext.ObjectType.GetProperties();

        foreach (var prop in props)
        {
            if (prop.CustomAttributes.Any(
                x => typeof(ValidationAttribute).IsAssignableFrom(x.AttributeType)
                ))
            {
                Validator.TryValidateProperty(
                    prop.GetValue(validationContext.ObjectInstance),
                    new ValidationContext(this, null, null)
                    {
                        MemberName = prop.Name
                    },
                    results
                    );
            }
        }
        return results;
    }
}

This Validate method ensures that, if a validateable (is validateable a word??) object contains properties whose types are also validateable objects, that those objects get validated as well. This is crucial for options objects, because they frequently contain other options objects as properties.

The method does that by using a little reflection to iterate through the object’s public properties. For each property it finds, it checks the type to see if it recursively validate the public properties on that object, as well. If it determines that it should, it then calls the Validator.TryValidateProperty method, on that property’s value, thereby validating that object as well.

Once I had my base classes and extension methods ready, I then moved on to create a type that I could use as a base for all my options classes. Here is what that class looks like:

public abstract class OptionsBase : ValidatableObject
{

}

The class is nothing more than a placeholder, at this point. That’s alright though, this placeholder allows me to write a quick concrete options class, like this:

public class MyOptions : OptionsBase
{
    [Required]
    public string A {get;set;}
}

Then, when I want to validate it, to ensure that the A property is, indeed, not null, or empty, I can simply do this:

var options = new MyOptions();
// TODO : bind the options to a configutation.
options.IsValid();
// or
options.ThrowIfInvalid();

Pretty cool, right? And because I’m using standard .NET data annotation attributes, I can add any of them to the properties on my options classes, and then validate them using the code above.

I’ll build on this validation capability when I discuss some of the other features within the CG.Options NUGET package. Stick around for that.

Photo by Victoriano Izquierdo on Unsplash