Larry Steinle

March 9, 2011

Argument Validation

Filed under: Security,VS.Net — Larry Steinle @ 9:07 pm
Tags: , , ,

I wanted to take a small break from the Active Directory Data Access Layer to discuss a best practice that should be considered when constructing a code library. The first priority when constructing a reusable code base is the class diagram. The structure of the classes and the names of the methods impact how easily a class library can be to implement. Equally important is the careful attention to detail for argument values.

Argument Validation Provides Valuable Insight

A code library must be intuitive. The consumer of the library always makes assumptions about how a method should behave based upon its name and the context of the container. What happens when a wrong assumption is applied to a method? Do you let the system throw its own error or do you throw a custom error?

A code library built for usability will perform argument checking and throw custom errors. The system error may have no meaning to the caller making the system confusing and difficult. Vague or inappropriate error messages discourage developers from using code libraries. The consumer of a poorly written code library will always prefer code snippets where they can see where the error is thrown and why the error was thrown.

Carefully consider when and how to validate arguments and throw meaningful errors. Argument validation is paramount to making a reusable library intuitive, reliable and simple to consume. Argument validation is just as important to making a reliable, usable code library as a well-thought out class diagram.

Argument Validation Strategy

Any time you find yourself writing the same code over and over is an opportunity to refactor. Refactoring code reduces the risk of errors due to copy/paste inheritance, reduces the amount of effort required to validate the code and reduces the amount of time required to implement the functionality while improving the overall stability of the system. Argument validation is no exception.

While using reflector to answer a few questions about how Microsoft implemented the DbConnection, DbCommand, DbDataReader and DbDataAdapter classes I discovered that Microsoft identified common validation checks and placed these checks into a class that was private to the library. When there was a validation check that was specific to the class they kept it in a routine scoped to the class.

Argument Validator

The argument validator is meant to be a private class within the code library. Each routine is marked as shared (static in c#) making it easier to use throughout the library.

VB.Net _ErrorValidator Code

''' <summary>
''' Manages throwing errors in a common and consistent manner throughout the library.
''' </summary>
''' <remarks></remarks>
Friend Class _ErrorValidator
  ''' <summary>
  ''' Throws an error after logging the message.
  ''' </summary>
  ''' <param name="ex">
  ''' The exception to throw.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowError(ByVal ex As System.Exception)
    'Here you could write the error to the event log
    'You could write to the Debug.WriteLine method
    'Or write to the Console.WriteLine method.
    'Or any other logging tool that you want to use.
    'Then throw the exception.
    Throw ex
  End Sub

  ''' <summary>
  ''' Throws an UnauthorizedAccessException message.
  ''' </summary>
  ''' <param name="message">
  ''' The message to throw.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnAudit(ByVal message As String)
    ThrowError(New System.UnauthorizedAccessException(message))
  End Sub

  ''' <summary>
  ''' Throws an InvalidOperationException when the object is closed.
  ''' </summary>
  ''' <param name="connection">
  ''' The object to test for connectivity.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnConnectionClosed(ByVal connection As System.Data.IDbConnection)
    If connection Is Nothing OrElse connection.State = System.Data.ConnectionState.Closed Then
      ThrowError(New InvalidOperationException("Connection closed."))
    End If
  End Sub

  ''' <summary>
  ''' Throws a DirectoryNotFoundException when the folder path is missing.
  ''' </summary>
  ''' <param name="path">
  ''' The folder path to find.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnDirectoryNotFound(ByVal path As String)
    If Not System.IO.Directory.Exists(path) Then
      ThrowError(New System.IO.DirectoryNotFoundException(String.Format("Missing or invalid path: {0}", path)))
    End If
  End Sub

  ''' <summary>
  ''' Throws an DriveNotFoundException when the drive letter can not be found on the current machine.
  ''' </summary>
  ''' <param name="<span class=" />driveLetter">
  ''' The drive letter to find.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnDriveNotFound(ByVal driveLetter As Char)
    For Each drive As System.IO.DriveInfo In System.IO.DriveInfo.GetDrives
      If drive.Name = driveLetter & ":\" Then Exit Sub
    Next
    ThrowError(New System.IO.DriveNotFoundException(String.Format("Missing or invalid drive letter: {0}.", driveLetter)))
  End Sub

  ''' <summary>
  ''' Throws an ArgumentNullException when the value is null or empty.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter argument to test.
  ''' </param>
  ''' <param name="value">
  ''' The parameter argument to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnEmpty(ByVal paramName As String, ByVal value As Object)
    ThrowErrorOnNull(paramName, value)
    If value.ToString.Length = 0 Then
      ThrowError(New ArgumentNullException(paramName, "The argument cannot be empty."))
    End If
  End Sub

  ''' <summary>
  ''' Throws an ArgumentException when the list is empty.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter to test.
  ''' </param>
  ''' <param name="list">
  ''' The list to test for values.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnEmptyList(ByVal paramName As String, ByVal list As System.Collections.IList)
    ThrowErrorOnNull(paramName, list)
    If list.Count = 0 Then
      ThrowError(New ArgumentException("The AdParameterCollection must contain at least one AdParameter instance.", paramName))
    End If
  End Sub

  ''' <summary>
  ''' Throws a FileNotFoundException when the specified file path does not exist.
  ''' </summary>
  ''' <param name="path">
  ''' The file path to validate.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnFileNotFound(ByVal path As String)
    If Not System.IO.File.Exists(path) Then
      ThrowError(New System.IO.FileNotFoundException("Missing or invalid path.", path))
    End If
  End Sub

  ''' <summary>
  ''' Throws an IndexOutOfRangeException when the index is greater than the number of fields in the row.
  ''' </summary>
  ''' <param name="index">
  ''' A zero-based ordinal value.
  ''' </param>
  ''' <param name="count">
  ''' The maximum number of items in the list.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnIndexOutOfRange(ByVal index As Integer, ByVal count As Integer)
    ThrowErrorOnIndexOutOfRange(index, 0, count - 1)
  End Sub

  ''' <summary>
  ''' Throws an IndexOutOfRangeException when the index is less than the
  ''' specified minimumRange or greater than the specified maximumRange.
  ''' </summary>
  ''' <param name="index">
  ''' The index value.
  ''' </param>
  ''' <param name="minimumRange">
  ''' The minimum range value.
  ''' </param>
  ''' <param name="maximumRange">
  ''' The maximum range value.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnIndexOutOfRange(ByVal index As Integer, ByVal minimumRange As Integer, ByVal maximumRange As Integer)
    If index < minimumRange OrElse index > maximumRange Then
      ThrowError(New IndexOutOfRangeException(String.Format("Index value of {0} out of range. Must be a value between {0} and {1}.", index, minimumRange, maximumRange)))
    End If
  End Sub

  ''' <summary>
  ''' Throws an ArgumentException when the value does not conform to the specified pattern.
  ''' </summary>
  ''' <param name="paramName">
  ''' The name of the argument.
  ''' </param>
  ''' <param name="value">
  ''' The string value to test.
  ''' </param>
  ''' <param name="pattern">
  ''' The regular expression used to enforce the pattern.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnInvalidFormat(ByVal paramName As String, ByVal value As String, ByVal pattern As String)
    If Not Text.RegularExpressions.Regex.IsMatch(value, pattern) Then
      ThrowError(New ArgumentException("Invalid format.", paramName))
    End If
  End Sub

  ''' <summary>
  ''' Throws an UnauthorizedAccessException when the path is not contained in the rootPath.
  ''' </summary>
  ''' <param name="path">
  ''' The path value to test.
  ''' </param>
  ''' <param name="rootPath">
  ''' The authorized parent path.
  ''' </param>
  ''' <remarks>
  ''' Use to identify potential cross-application attack.
  ''' </remarks>
  Public Shared Sub ThrowErrorOnInvalidRootPath(ByVal path As String, ByVal rootPath As String)
    If Not path.Trim.ToUpper.StartsWith(rootPath.Trim.ToUpper) Then
      ThrowError(New UnauthorizedAccessException("Invalid file path specified."))
    End If
  End Sub

  ''' <summary>
  ''' Throws an InvalidOperationException. Use when the state
  ''' of the object is invalid for the specified behavior.
  ''' </summary>
  ''' <param name="message">
  ''' The message to include with the error.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnInvalidState(ByVal message As String)
    ThrowError(New InvalidOperationException(message))
  End Sub

  ''' <summary>
  ''' Throws an IndexOutOfRangeException when the length of the value is greater than the specified length.
  ''' </summary>
  ''' <param name="value">
  ''' The string value to test for length.
  ''' </param>
  ''' <param name="length">
  ''' The maximum number of characters permitted in the string value.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnLengthExceeded(ByVal value As String, ByVal length As Integer)
    If value.Length > length Then
      ThrowError(New IndexOutOfRangeException(String.Format("String must be less than or equal to {0} characters in length", length.ToString)))
    End If
  End Sub

  ''' <summary>
  ''' Throws an IndexOutOfRangeException when the key is not found in the dictionary.
  ''' </summary>
  ''' <param name="value">
  ''' The dictionary to validate.
  ''' </param>
  ''' <param name="<span class=" />keyName">
  ''' The key value to search.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnMissingKey(ByVal value As Object, ByVal keyName As String)
    ThrowErrorOnMissingKey(Nothing, value, keyName)
  End Sub

  ''' <summary>
  ''' Throws an IndexOutOfRangeException when the key is not found in the dictionary.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter.
  ''' </param>
  ''' <param name="value">
  ''' The dictionary to validate.
  ''' </param>
  ''' <param name="<span class=" />keyName">
  ''' The key value to search.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnMissingKey(ByVal paramName As String, ByVal value As Object, ByVal keyName As String)
    If TypeOf value Is System.Collections.IDictionary Then
      For Each keyEntry As String In CType(value, System.Collections.IDictionary).Keys
        If String.Compare(keyEntry, keyName, True) = 0 Then Exit Sub
      Next

      If String.IsNullOrWhiteSpace(paramName) Then
        ThrowError(New IndexOutOfRangeException("Key not found in argument."))
      Else
        ThrowError(New IndexOutOfRangeException(String.Format("Key not found in {0} argument.", paramName)))
      End If
    End If
  End Sub

  ''' <summary>
  ''' Throws an ArgumentException when the value is a negative number.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the argument.
  ''' </param>
  ''' <param name="value">
  ''' The value to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnNegative(ByVal paramName As String, ByVal value As Object)
    If TypeOf value Is Decimal AndAlso Convert.ToDecimal(value) < 0 _
    OrElse TypeOf value Is Double AndAlso Convert.ToDouble(value) < 0 _
    OrElse TypeOf value Is Int16 AndAlso Convert.ToInt16(value) < 0 _
    OrElse TypeOf value Is Int32 AndAlso Convert.ToInt32(value) < 0 _
    OrElse TypeOf value Is Int64 AndAlso Convert.ToInt64(value) < 0 _
    OrElse TypeOf value Is Single AndAlso Convert.ToSingle(value) < 0 _
    OrElse TypeOf value Is UInt16 AndAlso Convert.ToUInt16(value) < 0 _
    OrElse TypeOf value Is UInt32 AndAlso Convert.ToUInt32(value) < 0 _
    OrElse TypeOf value Is UInt64 AndAlso Convert.ToUInt64(value) < 0 Then
      ThrowError(New ArgumentException("The argument must be a non-negative number.", paramName))
    End If
  End Sub

  ''' <summary>
  ''' Throws an NotImplementedException. Use when
  ''' the specified method does not have any code.
  ''' </summary>
  ''' <param name="<span class=" />methodName">
  ''' The name of the method that requires code to be
  ''' added before the class can be counted as completed.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnNotImplemented(ByVal methodName As String)
    ThrowError(New NotImplementedException(String.Format("The {0} method has not been implemented.", methodName)))
  End Sub

  ''' <summary>
  ''' Throws an ArgumentNullException when the value is null or DbNull.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter argument to test.
  ''' </param>
  ''' <param name="value">
  ''' The parameter argument to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnNull(ByVal paramName As String, ByVal value As Object)
    If value Is Nothing OrElse IsDBNull(value) Then
      ThrowError(New ArgumentNullException(paramName, "A value is required for the argument."))
    End If
  End Sub

  ''' <summary>
  ''' Throws an InvalidOperationException when the object is closed.
  ''' </summary>
  ''' <param name="reader">
  ''' The object to test for connectivity.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnReaderClosed(ByVal reader As System.Data.IDataReader)
    If reader Is Nothing OrElse reader.IsClosed Then
      ThrowError(New InvalidOperationException("Reader closed."))
    End If
  End Sub

  ''' <summary>
  ''' Throws an InvalidCastException when paramType is not of type checkType.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter argument to test.
  ''' </param>
  ''' <param name="<span class=" />checkType">
  ''' The expected type.
  ''' </param>
  ''' <param name="paramType">
  ''' The type to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnTypeMismatch(ByVal paramName As String, ByVal paramType As System.Type, ByVal checkType As System.Type)
    If Not checkType Is paramType Then
      ThrowError(New InvalidCastException(String.Format("{0} must be of type {1}.", paramName, checkType.Name)))
    End If
  End Sub

  ''' <summary>
  ''' Throws an ArgumentException when the value is null, empty or contains spaces.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter argument to test.
  ''' </param>
  ''' <param name="value">
  ''' The parameter argument to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnWhiteSpace(ByVal paramName As String, ByVal value As Object)
    ThrowErrorOnNull(paramName, value)
    If value.ToString.Trim.Length = 0 Then
      ThrowError(New ArgumentException(paramName, "The argument must have a value other than spaces."))
    End If
  End Sub

  ''' <summary>
  ''' Throws a DivideByZeroException when the value is zero.
  ''' </summary>
  ''' <param name="<span class=" />paramName">
  ''' The name of the parameter.
  ''' </param>
  ''' <param name="value">
  ''' The value to test.
  ''' </param>
  ''' <remarks></remarks>
  Public Shared Sub ThrowErrorOnZero(ByVal paramName As String, ByVal value As Object)
    If TypeOf value Is Decimal AndAlso Convert.ToDecimal(value) = 0 _
    OrElse TypeOf value Is Double AndAlso Convert.ToDouble(value) = 0 _
    OrElse TypeOf value Is Int16 AndAlso Convert.ToInt16(value) = 0 _
    OrElse TypeOf value Is Int32 AndAlso Convert.ToInt32(value) = 0 _
    OrElse TypeOf value Is Int64 AndAlso Convert.ToInt64(value) = 0 _
    OrElse TypeOf value Is Single AndAlso Convert.ToSingle(value) = 0 _
    OrElse TypeOf value Is UInt16 AndAlso Convert.ToUInt16(value) = 0 _
    OrElse TypeOf value Is UInt32 AndAlso Convert.ToUInt32(value) = 0 _
    OrElse TypeOf value Is UInt64 AndAlso Convert.ToUInt64(value) = 0 Then
      ThrowError(New DivideByZeroException(String.Format("The {0} argument cannot be zero.", paramName)))
    End If
  End Sub
End Class

C#.Net _ErrorValidator Code

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;

namespace LarrySteinle.Library
{
  /// <summary>
  /// Manages throwing errors in a common and consistent manner throughout the library.
  /// </summary>
  /// <remarks></remarks>
  internal class _ErrorValidator
  {
    /// <summary>
    /// Throws an error after logging the message.
    /// </summary>
    /// <param name="ex">
    /// The exception to throw.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowError(System.Exception ex)
    {
      //Here you could write the error to the event log
      //You could write to the Debug.WriteLine method
      //Or write to the Console.WriteLine method.
      //Or any other logging tool that you want to use.
      //Then throw the exception.
      throw (ex);
    }

    /// <summary>
    /// Throws an UnauthorizedAccessException message.
    /// </summary>
    /// <param name="message">
    /// The message to throw.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnAudit(string message)
    {
      ThrowError(new System.UnauthorizedAccessException(message));
    }

    /// <summary>
    /// Throws an InvalidOperationException when the object is closed.
    /// </summary>
    /// <param name="connection">
    /// The object to test for connectivity.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnConnectionClosed(IDbConnection connection)
    {
      if ((connection == null) || (connection.State == ConnectionState.Closed))
        ThrowError(new InvalidOperationException("Connection closed."));
    }

    /// <summary>
    /// Throws a DirectoryNotFoundException when the folder path is missing.
    /// </summary>
    /// <param name="path">
    /// The folder path to find.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnDirectoryNotFound(string path)
    {
      if (!Directory.Exists(path))
        ThrowError(new System.IO.DirectoryNotFoundException(string.Format("Missing or invalid path: {0}", path)));
    }

    /// <summary>
    /// Throws an DriveNotFoundException when the drive letter can not be found on the current machine.
    /// </summary>
    /// <param name="driveLetter">
    /// The drive letter to find.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnDriveNotFound(char driveLetter)
    {
      bool foundDrive = false;
      foreach (DriveInfo drive in DriveInfo.GetDrives())
        if (drive.Name == driveLetter + ":\\")
        {
          foundDrive = true;
          break;
        }

      if (!foundDrive)
        ThrowError(new System.IO.DriveNotFoundException(string.Format("Missing or invalid drive letter: {0}.", driveLetter)));
    }

    /// <summary>
    /// Throws an ArgumentNullException when the value is null or empty.
    /// </summary>
    /// <param name="<span class=" />paramName">
    /// The name of the parameter argument to test.
    /// </param>
    /// <param name="value">
    /// The parameter argument to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnEmpty(string paramName, object value)
    {
      ThrowErrorOnNull(paramName, value);
      if (value.ToString().Length == 0)
        ThrowError(new ArgumentNullException(paramName, "The argument cannot be empty."));
    }

    /// <summary>
    /// Throws an ArgumentException when the list is empty.
    /// </summary>
    /// <param name="<span class=" />paramName">
    /// The name of the parameter to test.
    /// </param>
    /// <param name="list">
    /// The list to test for values.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnEmptyList(string paramName, System.Collections.IList list)
    {
      ThrowErrorOnNull(paramName, list);
      if (list.Count == 0)
        ThrowError(new ArgumentException("The AdParameterCollection must contain at least one AdParameter instance.", paramName));
    }

    /// <summary>
    /// Throws a FileNotFoundException when the specified file path does not exist.
    /// </summary>
    /// <param name="path">
    /// The file path to validate.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnFileNotFound(string path)
    {
      if (!File.Exists(path))
        ThrowError(new System.IO.FileNotFoundException("Missing or invalid path.", path));
    }

    /// <summary>
    /// Throws an IndexOutOfRangeException when the index is greater than the number of fields in the row.
    /// </summary>
    /// <param name="index">
    /// A zero-based ordinal value.
    /// </param>
    /// <param name="count">
    /// The maximum number of items in the list.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnIndexOutOfRange(int index, int count)
    {
      ThrowErrorOnIndexOutOfRange(index, 0, count - 1);
    }

    /// <summary>
    /// Throws an IndexOutOfRangeException when the index is less than the
    /// specified minimumRange or greater than the specified maximumRange.
    /// </summary>
    /// <param name="index">
    /// The index value.
    /// </param>
    /// <param name="minimumRange">
    /// The minimum range value.
    /// </param>
    /// <param name="maximumRange">
    /// The maximum range value.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnIndexOutOfRange(int index, int minimumRange, int maximumRange)
    {
      if ((index < minimumRange) || (index > maximumRange))
        ThrowError(new IndexOutOfRangeException(String.Format("Index value of {0} out of range. Must be a value between {0} and {1}.", index, minimumRange, maximumRange)));
    }

    /// <summary>
    /// Throws an ArgumentException when the value does not conform to the specified pattern.
    /// </summary>
    /// <param name="paramName">
    /// The name of the argument.
    /// </param>
    /// <param name="value">
    /// The string value to test.
    /// </param>
    /// <param name="pattern">
    /// The regular expression used to enforce the pattern.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnInvalidFormat(string paramName, string value, string pattern)
    {
      if (!System.Text.RegularExpressions.Regex.IsMatch(value, pattern))
        ThrowError(new ArgumentException("Invalid format.", paramName));
    }

    /// <summary>
    /// Throws an UnauthorizedAccessException when the path is not contained in the rootPath.
    /// </summary>
    /// <param name="path">
    /// The path value to test.
    /// </param>
    /// <param name="rootPath">
    /// The authorized parent path.
    /// </param>
    /// <remarks>
    /// Use to identify potential cross-application attack.
    /// </remarks>
    public static void ThrowErrorOnInvalidRootPath(string path, string rootPath)
    {
      if (!path.Trim().ToUpper().StartsWith(rootPath.Trim().ToUpper()))
        ThrowError(new UnauthorizedAccessException("Invalid file path specified."));
    }

    /// <summary>
    /// Throws an InvalidOperationException. Use when the state
    /// of the object is invalid for the specified behavior.
    /// </summary>
    /// <param name="message">
    /// The message to include with the error.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnInvalidState(string message)
    {
      ThrowError(new InvalidOperationException(message));
    }

    /// <summary>
    /// Throws an IndexOutOfRangeException when the length of the value is greater than the specified length.
    /// </summary>
    /// <param name="value">
    /// The string value to test for length.
    /// </param>
    /// <param name="length">
    /// The maximum number of characters permitted in the string value.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnLengthExceeded(string value, int length)
    {
      if (value.Length > length)
        ThrowError(new IndexOutOfRangeException(string.Format("String must be less than or equal to {0} characters in length", length)));
    }

    /// <summary>
    /// Throws an IndexOutOfRangeException when the key is not found in the dictionary.
    /// </summary>
    /// <param name="value">
    /// The dictionary to validate.
    /// </param>
    /// <param name="keyName">
    /// The key value to search.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnMissingKey(object value, string keyName)
    {
      ThrowErrorOnMissingKey(null, value, keyName);
    }

    /// <summary>
    /// Throws an IndexOutOfRangeException when the key is not found in the dictionary.
    /// </summary>
    /// <param name="paramName">
    /// The name of the parameter.
    /// </param>
    /// <param name="value">
    /// The dictionary to validate.
    /// </param>
    /// <param name="keyName">
    /// The key value to search.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnMissingKey(string paramName, object value, string keyName)
    {
      if (value is IDictionary)
      {
        bool foundKey = false;
        foreach (string keyEntry in ((System.Collections.IDictionary)value).Keys)
        {
          if (string.Compare(keyEntry, keyName, true) == 0)
          {
            foundKey = true;
            break;
          }
        }

        if (!foundKey)
        {
          if (string.IsNullOrWhiteSpace(paramName))
            ThrowError(new IndexOutOfRangeException("Key not found in argument."));
          else
            ThrowError(new IndexOutOfRangeException(string.Format("Key not found in {0} argument.", paramName)));
        }
      }
    }

    /// <summary>
    /// Throws an ArgumentException when the value is a negative number.
    /// </summary>
    /// <param name="paramName">
    /// The name of the argument.
    /// </param>
    /// <param name="value">
    /// The value to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnNegative(string paramName, object value)
    {
      if (
         ((value is Decimal) && (Convert.ToDecimal(value) < 0))
      || ((value is Double) && (Convert.ToDouble(value) < 0))
      || ((value is Int16) && (Convert.ToInt16(value) < 0))
      || ((value is Int32) && (Convert.ToInt32(value) < 0))
      || ((value is Int64) && (Convert.ToInt64(value) < 0))
      || ((value is Single) && (Convert.ToSingle(value) < 0))
      || ((value is UInt16) && (Convert.ToUInt16(value) < 0))
      || ((value is UInt32) && (Convert.ToUInt32(value) < 0))
      || ((value is UInt64) && (Convert.ToUInt64(value) < 0))
      )
        ThrowError(new ArgumentException("The argument must be a non-negative number.", paramName));
    }

    /// <summary>
    /// Throws an NotImplementedException. Use when
    /// the specified method does not have any code.
    /// </summary>
    /// <param name="methodName">
    /// The name of the method that requires code to be
    /// added before the class can be counted as completed.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnNotImplemented(string methodName)
    {
      ThrowError(new NotImplementedException(string.Format("The {0} method has not been implemented.", methodName)));
    }

    /// <summary>
    /// Throws an ArgumentNullException when the value is null or DbNull.
    /// </summary>
    /// <param name="paramName">
    /// The name of the parameter argument to test.
    /// </param>
    /// <param name="value">
    /// The parameter argument to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnNull(string paramName, object value)
    {
      if ((value == null) || (value == DBNull.Value))
        ThrowError(new ArgumentNullException(paramName, "A value is required for the argument."));
    }

    /// <summary>
    /// Throws an InvalidOperationException when the object is closed.
    /// </summary>
    /// <param name="reader">
    /// The object to test for connectivity.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnReaderClosed(System.Data.IDataReader reader)
    {
      if ((reader == null) || (reader.IsClosed))
        ThrowError(new InvalidOperationException("Reader closed."));
    }

    /// <summary>
    /// Throws an InvalidCastException when paramType is not of type checkType.
    /// </summary>
    /// <param name="paramName">
    /// The name of the parameter argument to test.
    /// </param>
    /// <param name="checkType">
    /// The expected type.
    /// </param>
    /// <param name="paramType">
    /// The type to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnTypeMismatch(string paramName, System.Type paramType, System.Type checkType)
    {
      if (!(checkType == paramType))
        ThrowError(new InvalidCastException(string.Format("{0} must be of type {1}.", paramName, checkType.Name)));
    }

    /// <summary>
    /// Throws an ArgumentException when the value is null, empty or contains spaces.
    /// </summary>
    /// <param name="paramName">
    /// The name of the parameter argument to test.
    /// </param>
    /// <param name="value">
    /// The parameter argument to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnWhiteSpace(string paramName, object value)
    {
      ThrowErrorOnNull(paramName, value);
      if (value.ToString().Trim().Length == 0)
        ThrowError(new ArgumentException(paramName, "The argument must have a value other than spaces."));
    }

    /// <summary>
    /// Throws a DivideByZeroException when the value is zero.
    /// </summary>
    /// <param name="paramName">
    /// The name of the parameter.
    /// </param>
    /// <param name="value">
    /// The value to test.
    /// </param>
    /// <remarks></remarks>
    public static void ThrowErrorOnZero(string paramName, object value)
    {
      if (
         ((value is Decimal) && (Convert.ToDecimal(value) == 0))
      || ((value is Double) && (Convert.ToDouble(value) == 0))
      || ((value is Int16) && (Convert.ToInt16(value) == 0))
      || ((value is Int32) && (Convert.ToInt32(value) == 0))
      || ((value is Int64) && (Convert.ToInt64(value) == 0))
      || ((value is Single) && (Convert.ToSingle(value) == 0))
      || ((value is UInt16) && (Convert.ToUInt16(value) == 0))
      || ((value is UInt32) && (Convert.ToUInt32(value) == 0))
      || ((value is UInt64) && (Convert.ToUInt64(value) == 0))
      )
        ThrowError(new DivideByZeroException(string.Format("The {0} argument cannot be zero.", paramName)));
    }
  }
}

Summary

In today’s post we reviewed the reasons to validate arguments:

  • Make the system more intuitive by providing meaningful error messages to the caller.
  • Improve the reliability of the library by reducing the risk of acting against invalid data.
Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: