Ronin Building Blocks – Parameter Validation
It is time to get started on the first building block. There are so many options to choose from when we are at the beginning of the project. I have decided to start with Parameter Validation seeing my decision was to have a security first mindset.
The reason I chose this to start with, although not glamorous or for that matter fun, it really is typically a good practice to validate all parameters provided through public methods. With no further delays we will build the first block that will be known as ‘Ronin Parameters’.
Creating the solution was done as any other Visual Studio solution but when I added the library I had to select the ‘’ template as seen here.
The first thing I did, as I always do, was to delete the Class1.cs file. After the project was cleaned up I needed to adjust the project.json to make sure the target framework was correct and my desired properties and build behavior was applied. The following is the new project.json file after the modifications
{
"version": "1.0.1-*",
"name": "Ronin.Parameters",
"description": "Common parameter functions including validation.",
"copyright": "Microsoft 2016",
"title": "Ronin Parameters",
"authors": [ "Chris Clayton" ],
"language": "en-US",
"buildOptions": {
"optimize": true,
"embed": { "include": [ "Resources/GuardMessages.resx" ] },
"warningsAsErrors": true,
"xmlDoc": true,
"platform": "anycpu",
"debugType": "portable"
},
"frameworks": {
"netstandard1.6": { "dependencies": { "NETStandard.Library": "1.6.*" } },
"net451": {}
}
}
Next let’s add the GuardException.cs to the project. This type will derive from the ArgumentException type so it can be raised anytime a parameter does not adhere to expected conditions.
namespace Ronin.Parameters
{
#region Using Clauses
using System;
#endregion
/// <summary>
/// An exception derived from ArgumentException to allow handlers to take special actions in handling
/// </summary>
public class GuardException : ArgumentException
{
#region Constructors
/// <summary>
/// Initializes a new instance of the GuardException class with a specified error message and the parameter name that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception. </param>
/// <param name="paramName">The name of the parameter that caused the current exception. </param>
public GuardException(string message, string paramName) : base(message, paramName)
{
}
/// <summary>
/// Initializes a new instance of the GuardException class with a specified error message, the parameter name, and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception. </param>
/// <param name="paramName">The name of the parameter that caused the current exception. </param>
/// <param name="innerException">The exception that is the cause of the current exception. If the innerException parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception.</param>
public GuardException(string message, string paramName, Exception innerException) : base(message, paramName, innerException)
{
}
#endregion
}
}
In the implementation, all the constructor signatures will require a parameter name as well as a message. This makes sure that the consumer can have enough information to take the appropriate action.
As you can see from the code above I have learned over time that clean documentation is important for the ongoing support of a product. You cannot believe the number of times that I have opened code to and spent far longer than I should have to because there were minimal or no comments. Project Ronin will have lots of comments and documentation.
Now I will add the GuardMessages.resx file that contains the parameter value violation messages. As noted earlier these messages will be in en-US exclusively for this project and this is an example of the format of the messages used.
Name: NotDefault
Value: The parameter {0} cannot be the default value for the type.
The final step is to add the Guard.cs class that will do the heavy lifting. The following code snipped is an example of the guard class having a set of common validation methods.
namespace Ronin.Parameters
{
#region Using Clauses
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Globalization;
using System.Text.RegularExpressions;
using Resources;
#endregion
/// <summary>
/// A set of methods used to validate publicly input parameters.
/// </summary>
public static class Guard
{
#region Public Guard Methods
/// <summary>
/// Tests to make sure the supplied parameter value is not null. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void NotNull<T>(string parameterName, T parameterValue) where T : class
{
if (parameterValue == null)
{
throw new GuardException(parameterName, string.Format(GuardMessages.NotNull, parameterName));
}
}
/// <summary>
/// Tests to make sure the supplied parameter is not the default value for the type. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void NotDefault<T>(string parameterName, T parameterValue)
{
if (Equals(parameterValue, default(T)))
{
throw new GuardException(parameterName, string.Format(GuardMessages.NotDefault, parameterName));
}
}
/// <summary>
/// Tests to make sure the supplied string parameter is not null or empty.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void NotNullOrEmpty(string parameterName, string parameterValue)
{
NotNull(parameterName, parameterValue);
if (string.IsNullOrEmpty(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.NotNullOrEmpty, parameterName), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied string parameter is not null or all whitespace.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void NotNullOrWhitespace(string parameterName, string parameterValue)
{
NotNull(parameterName, parameterValue);
if (string.IsNullOrWhiteSpace(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.NotNullOrWhitespace, parameterName), parameterName);
}
}
/// <summary>
/// Tests to see if the directory provided in the parameter value exists.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void DirectoryExists(string parameterName, string parameterValue)
{
NotNullOrWhitespace(parameterName, parameterValue);
if (!Directory.Exists(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.DirectoryExists, parameterName), parameterName);
}
}
/// <summary>
/// Tests to see if the directory provided in the parameter value exists.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void DirectoryNotExists(string parameterName, string parameterValue)
{
NotNullOrWhitespace(parameterName, parameterValue);
if (Directory.Exists(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.DirectoryNotExists, parameterName), parameterName);
}
}
/// <summary>
/// Tests to see if the file provided in the parameter value exists.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void FileExists(string parameterName, string parameterValue)
{
NotNullOrWhitespace(parameterName, parameterValue);
if (!File.Exists(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.FileExists, parameterName), parameterName);
}
}
/// <summary>
/// Tests to make sure the file provided in the parameter does not exist.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void FileNotExists(string parameterName, string parameterValue)
{
NotNullOrWhitespace(parameterName, parameterValue);
if (File.Exists(parameterValue))
{
throw new GuardException(string.Format(GuardMessages.FileNotExists, parameterName), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied parameter value is not less than the minimum value. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="minimumValue">The minimum allowable value of the parameter.</param>
public static void NotLessThan(string parameterName, long parameterValue, long minimumValue)
{
if (parameterValue < minimumValue)
{
throw new GuardException(string.Format(GuardMessages.NotLessThan, parameterName, minimumValue), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied parameter value is not less than the minimum value. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="maximumValue">The maximum allowable value of the parameter.</param>
public static void NotMoreThan(string parameterName, long parameterValue, long maximumValue)
{
if (parameterValue > maximumValue)
{
throw new GuardException(string.Format(GuardMessages.NotMoreThan, parameterName, maximumValue), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied parameter value is not less than the minimum value. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="maximumValue">The maximum allowable value of the parameter.</param>
public static void NotMoreThan(string parameterName, ulong parameterValue, ulong maximumValue)
{
if (parameterValue > maximumValue)
{
throw new GuardException(string.Format(GuardMessages.NotMoreThan, parameterName, maximumValue), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied parameter value is not less than the minimum value. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="minimumValue">The minimum allowable value of the parameter.</param>
public static void NotLessThan(string parameterName, ulong parameterValue, ulong minimumValue)
{
if (parameterValue < minimumValue)
{
throw new GuardException(string.Format(GuardMessages.NotLessThan, parameterName, minimumValue), parameterName);
}
}
/// <summary>
/// Tests to make sure the supplied parameter value is not less than the minimum value. If it is a Guard Exception will be raised.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="minimumValue">The minimum allowable value of the parameter.</param>
public static void NotLessThan(string parameterName, float parameterValue, float minimumValue)
{
if (parameterValue < minimumValue)
{
throw new GuardException(string.Format(GuardMessages.NotLessThan, parameterName, minimumValue), parameterName);
}
}
/// <summary>
/// Tests to make sure that a parameter representing a buffer position offset is not outside of the allowable bounds the specified enumerable.
/// </summary>
/// <typeparam name="T">The type of elements in the enumerable.</typeparam>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="items">The enumeration of items being validated againts.</param>
/// <param name="offset">The offset that must exist within the bounds.</param>
public static void IndexOutOfRange<T>(string parameterName, IEnumerable<T> items, int offset)
{
if (items == null || items.Count() <= offset)
{
throw new GuardException(string.Format(GuardMessages.IndexOutOfRange, parameterName), parameterName);
}
}
/// <summary>
/// Tests to make sure that the beginning and end of the segment provided will not be outside of the enumerables bounds.
/// </summary>
/// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="items">The enumeration of items being validated against.</param>
/// <param name="offset">The offset that the segment starts at.</param>
/// <param name="count">The number of elements in the segment.</param>
public static void SegmentOutOfRange<T>(string parameterName, IEnumerable<T> items, int offset, int count)
{
var itemCount = items?.Count() ?? -1;
if (itemCount < 1 || offset < 0 || itemCount < offset + count)
{
throw new GuardException(string.Format(GuardMessages.SegmentOutOfRange, parameterName), parameterName);
}
}
/// <summary>
/// Raises a GuardException if the value is not assignable to the desired type.
/// </summary>
/// <typeparam name="T">The type that the value must be assignable from.</typeparam>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
public static void AssignableFrom<T>(string parameterName, object parameterValue) where T : class
{
if (!(parameterValue is T))
{
throw new GuardException(string.Format(GuardMessages.AssignableFrom, parameterName, typeof(T).Name), parameterName);
}
}
/// <summary>
/// Raises a GuardException if the value of the paraemter has more or less elements than identified.
/// </summary>
/// <typeparam name="T">The type of the array being validated.</typeparam>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="parameterValue">The value of the parameter being validated.</param>
/// <param name="minimumNumberOfElements">The minimum number of elements the array must contain.</param>
/// <param name="maximumNumberOfElements">The maximum number of elements the array must contain.</param>
public static void ElementCountInRange<T>(string parameterName, T[] parameterValue, int minimumNumberOfElements, int maximumNumberOfElements)
{
if (parameterValue.Length < minimumNumberOfElements || parameterValue.Length > maximumNumberOfElements)
{
throw new GuardException(string.Format(GuardMessages.ElementCountInRange, parameterName, minimumNumberOfElements, maximumNumberOfElements, parameterValue.Length), parameterName);
}
}
/// <summary>
/// Checks whether or not the specified collection is empty.
/// </summary>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValues">The values of the argument.</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void CollectionNotEmpty<T>(string parameterName, IEnumerable<T> argumentValues)
{
if (argumentValues == null || !argumentValues.Any())
{
throw new GuardException(String.Format(CultureInfo.CurrentCulture, GuardMessages.CollectionCannotBeEmpty, parameterName), parameterName);
}
}
/// <summary>
/// Checks an argument to ensure its value is expected value
/// </summary>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValue">The value of the argument.</param>
/// <param name="expectedValue">The expected value of the argument.</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void IsEqual<T>(string parameterName, T argumentValue, T expectedValue)
{
if (Comparer<T>.Default.Compare(argumentValue, expectedValue) != 0)
{
throw new GuardException(String.Format(CultureInfo.CurrentCulture, GuardMessages.InvalidArgumentValue, parameterName, expectedValue), parameterName);
}
}
/// <summary>
/// Checks that string argument value matches the given regex.
/// </summary>
/// <param name="argumentValue">The value of the argument.</param>
/// <param name="pattern">The regex pattern match.</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void NotNullAndMatchRegex(string parameterName, string argumentValue, string pattern)
{
NotNull(parameterName, argumentValue);
if (!Regex.IsMatch(argumentValue, pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(10)))
{
throw new GuardException(String.Format(CultureInfo.CurrentCulture, GuardMessages.StringMustMatchRegex, parameterName, pattern), parameterName);
}
}
/// <summary>
/// Checks that all values of the specified argument satisfy a given condition.
/// </summary>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValues">The values of the argument.</param>
/// <param name="predicate">The condition to satisfy.</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void SatisfyCondition<T>(string parameterName, IEnumerable<T> argumentValues, Func<T, bool> predicate)
{
if (argumentValues != null && !argumentValues.All(predicate))
{
throw new GuardException(String.Format(CultureInfo.CurrentCulture, GuardMessages.ConditionNotSatisfied, parameterName), parameterName);
}
}
/// <summary>
/// Checks if the supplied argument falls into the given range of values.
/// </summary>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValue">The value of the argument.</param>
/// <param name="minValue">The minimum allowed value of the argument.</param>
/// <param name="maxValue">The maximum allowed value of the argument.</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void InRange<T>(string parameterName, T argumentValue, T minValue, T maxValue) where T : IComparable<T>
{
if (Comparer<T>.Default.Compare(argumentValue, minValue) < 0 || Comparer<T>.Default.Compare(argumentValue, maxValue) > 0)
{
throw new GuardException(String.Format(CultureInfo.CurrentCulture, GuardMessages.CannotBeOutOfRange, parameterName, minValue, maxValue), parameterName);
}
}
/// <summary>
/// Checks if the supplied argument present in the collection of possible values.
/// </summary>
/// <remarks>
/// Comparison is case sensitive
/// </remarks>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValue">The value of the argument.</param>
/// <param name="collection">Collection of possible values</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void InCollection<T>(string parameterName, T argumentValue, ICollection<T> collection) where T : IComparable<T>
{
NotNull(parameterName, collection);
if (!(collection?.Contains(argumentValue) ?? false))
{
throw new GuardException(string.Format(CultureInfo.CurrentCulture, GuardMessages.NotInCollection, parameterName, argumentValue), parameterName);
}
}
/// <summary>
/// Checks if the supplied argument present in the collection of possible values.
/// </summary>
/// <remarks>
/// Comparison is case sensitive
/// </remarks>
/// <typeparam name="T">The type of the argument.</typeparam>
/// <param name="argumentValue">The value of the argument.</param>
/// <param name="array">Array of possible values</param>
/// <param name="parameterName">The name of the parameter for diagnostic purposes.</param>
public static void InArray<T>(string parameterName, T argumentValue, T[] array) where T : IComparable<T>
{
NotNull(parameterName, array);
if (!array.Contains(argumentValue))
{
throw new GuardException(string.Format(CultureInfo.CurrentCulture, GuardMessages.NotInArray, parameterName, argumentValue), parameterName);
}
}
/// <summary>
/// Checks an enum instance to ensure that its value is defined by the specified enum type.
/// </summary>
/// <typeparam name="T">The type of the enum.</typeparam>
/// <param name="enumValue">The enum value to check.</param>
/// <param name="parameterName">The name of the parameter holding the value.</param>
/// <remarks>
/// This method does not currently support Flags enums.
/// The constraint on the method should be updated to "enum" once the C# compiler supports it.
/// </remarks>
public static void EnumValueIsDefined<T>(string parameterName, T enumValue) where T : struct
{
if (!Enum.IsDefined(typeof(T), enumValue))
{
throw new GuardException(string.Format(CultureInfo.CurrentCulture, GuardMessages.InvalidEnumValue, parameterName, typeof(T)), parameterName);
}
}
#endregion
}
}
That finishes the first building block. Many of the assumptions and framework selection etc. in future building blocks will be the same as this one so I will not dig into them in the following posts unless they are different.