Validation

.Validation
Debug and Trace | Trace Listeners | TryParse Methods | Exceptions | Code Contracts

"There are a lot of ways to get data into an application. The moment you trust any of these ways you are introducing instability into your application."

Data validation is testing the values which are introduced into an application against expected values and ranges. Robust, secure applications can handle invalid data, especially when the invalid data is from user input. Malicious techniques, such as SQL injection, need to be anticipated and prevented from corrupting databases. Additionally, computers are finite state machines so the range of data needs to be validated to prevent data values, and stack limitations, from exceeding their limits. If invalid data is not caught by the application it can cause:

  1. Incorrect logic results.
  2. Undesirable side-effects.
  3. Data capacity or stack overflows.
  4. Security intrusions, malicious corruption of data.




Debug and Trace

.Assert Dialog in Debug and Trace Classes
Trace Asserts Work in Both Debug and Release Builds.
Debug Asserts Work in Debug Builds.

The System.Diagnostics namespace provides classes which allow for interaction with system processes. Two classes which are useful for testing data and communicating with the user are the Debug and Trace classes.

The Debug class provides methods which only work when the project is built in the Debug mode. The Assert method will display a message if a specified condition (an assertion) evaluates to false. The Fail method behaves like an assert which always evaluates to false. When the assertion evaluates to false, or the Fail method is called. A dialog box is displayed which allows the user to ignore the condition or abort the program. Also, if you are debugging the application with a debugger, by default the assert and fails messages will display in the Debug output window (Ctrl-Alt-O). The Debug class also contains write methods (Write(), WriteLine(), WriteIf(), WriteLineIf()) which do not display a dialog box, but provides a way to write information to the Debug output windows. (by default).

Debug.Fail ("The input file cust.txt was not found.");

The Trace class provides the methods as the Debug methods listed above. However the Trace methods were to designed to run again Release builds to allow for their actions to take place in a production environment. For example a Debug.Assert will only work in a Debug project build. While a Trace.Assert will work in both Debug build and a Release builds. Additionally the Trace class contains methods which allows you print system information along with your custom message. These methods are: TraceError(), TraceInformation(), TraceWarning(), With the three additional Trace methods you can also use the IndentSize and IdentLevel properties to format the output.

The following programs contains an assertion in both the Debug and Trace class. The Trace assertion run in both Debug and Release builds, while the Debug assertion only runs in a Debug build.

Debug and Trace Assertions

using System;
using System.Diagnostics;

namespace AssertExample
{
    class Program
    {
        static void Main()
        {
            int myInt = -5;

            Debug.Assert(myInt > 0, "Debug: Invalid Value", "myInt must be positive");
            Trace.Assert(myInt > 0, "Trace: Invalid Value", "myInt must be positive");
        }
    }
}

Top



Trace Listeners

The Listeners property of the Debug and Trace classes contain a static collection of TraceListeners which determine where the output from the Debug and Trace commands will be written. By default the only listener in the collection is the DefaultTraceListener. There are several predefined TraceListeners which can be used in the collection, or you can create a custom TraceListener by deriving from the TraceListener class. The DefaultTraceListener can be removed for the collection which will prevent the dialog box from displaying on the Fail and Assert. The DefaultTraceListener is the only Listener which displays a dialog box. Other predefined TraceListeners which can be used in the collection include:

  1. TextWriterTraceListener - writes to a stream of a file. Further derived classes include:
  2. EventLogTraceListener
  3. EventProviderTraceListener
  4. WebPageTraceListener
Methods and Properties

The Clear() method is used to remove all the trace listeners from the collection. The Add() method will add trace listeners to the collection.

// Clear Trace Listeners from the Collection
Trace.Listeners.Clear();

// Add a Text Write to Append to a File
Trace.Listeners.Add (new TextWriterTraceListener("trace.txt"));

Some listeners write to a cached stream (e.g. TextWriterTraceListener). The Flush() method will flush any cached data from the stream. The Close() method will also flush cached data when it closes the stream. The Trace and Debug also have an AutoFlush property which will force a flush after every message when the property is set to true. It is recommended to set the AutoFlush property to true so that debugging information is not lost should the application crash.

The Trace class has three methods not found in the Debug class which allow the indention of text and the display of system information along with your custom message. These methods TraceError(), TraceInformation(), TraceWarning() can use the TraceOptions property to add system information to the output, including such information as:

Trace Options
  1. Callstack - write the call stack, which is represented by the return value of the Environment.StackTrace property.
  2. DateTime- write the date and time.
  3. LogicalOperationStack - write the logical operation stack, which is represented by the return value of the CorrelationManager.LogicalOperationStack property.
  4. None- do not write any elements.
  5. ProcessId - write the process identity, which is represented by the return value of the Process.Id property.
  6. ThreadId - write the thread identity, which is represented by the return value of the Thread.ManagedThreadId property for the current thread.
  7. Timestamp - write the timestamp, which is represented by the return value of the GetTimestamp method.

The following program creates a trace listener (TextWriterTraceListener) to log messages to a file and a trace listener (ConsoleTraceListener) to write messages to the screen. The messages which are logged to the file additionally contain the DateTime and the Callstack system information.

.TraceLog and Console Messages
Logging Information to a File and Displaying on the Screen

using System.Diagnostics;

namespace TraceListenerExample
{
    class Program
    {
        static void Main()
        {
            // Clear Trace Listeners and Set AutoFlush
            Trace.Listeners.Clear();
            Trace.AutoFlush = true;          

            // First Trace Listener to Text File
            TextWriterTraceListener myTraceListener1 = new TextWriterTraceListener("ktrace.txt");
            myTraceListener1.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;
            Trace.Listeners.Add(myTraceListener1);

            // Second Trace Listener to Standard Output (Console)
            ConsoleTraceListener myConsoleTraceListener = new ConsoleTraceListener();
            Trace.Listeners.Add(myConsoleTraceListener);

            // Write Error Message
            Trace.TraceError("Bad Error X666.");

            // Write Informational Message
            Trace.TraceInformation("The important file was deleted.");
        }
    }
}

Top



TryParse Methods

Several built-in types in .NET implement TryParse methods. TryParse methods adhere to the try-parse pattern in that they test an argument before parsing it. For example, DateTime defines a Parse method that throws an exception if parsing of a string fails. It also defines a corresponding TryParse method that attempts to parse, but returns false if parsing is unsuccessful and returns the result of a successful parsing using an out parameter. Some of the types which have TryParse methods are:

The NumberStyles Enumeration determines the styles permitted in numeric string arguments that are passed to the Parse and TryParse methods of the integral and floating-point numeric types. For example, to TryParse a currency value, you would use the decimal method with NumberStyles.Currency.

TryParse a Currency Value
decimal.TryParse("$10.00", NumberStyles.Currency, cultureInfo, out result);




The NumberStyles enumeration consists of two kinds of enumeration values individual field flags and composite number styles. The following table lists the composite number styles and indicates which individual field flags they include.

Individual Field Flags in Composite Number Styles

Any

Currency

Float

Integer

Number

HexNumber

AllowHexSpecifier (0x0200)

0

0

0

0

0

1

AllowCurrencySymbol (0x0100)

1

1

0

0

0

0

AllowExponent (0x0080)

1

0

1

0

0

0

AllowThousands (0x0040)

1

1

0

0

1

0

AllowDecimalPoint (0x0020)

1

1

1

0

1

0

AllowParentheses (0x0010)

1

1

0

0

0

0

AllowTrailingSign (0x0008)

1

1

0

0

1

0

AllowLeadingSign (0x0004)

1

1

1

1

1

0

AllowTrailingWhite (0x0002)

1

1

1

1

1

1

AllowLeadingWhite (0x0001)

1

1

1

1

1

1

(0x1ff)

(0x17f)

(0x0a7)

(0x007)

(0x06f)

(0x203)

Top



Exceptions

A commonly used data validation technique is to throw an exception (ArgumentNullException, ArgumentException) when a null or invalid argument is passed to a method . This will prevent the program from proceeding when invalid data is passed to a method. Some suggestions from Microsoft for throwing an exception include:

  1. Do not return error codes. Exceptions are the primary means of reporting errors in frameworks.
  2. Do report execution failures by throwing exceptions. If a member cannot successfully do what it is designed to do, that should be considered an execution failure and an exception should be thrown.
  3. Do not have public members that can either throw or not throw exceptions based on some option.
  4. Do not have public members that return exceptions as the return value or as an out parameter.
  5. Consider the performance implications of throwing exceptions.
  6. Consider terminating the process by calling System.Environment.FailFast(System.String) (a .NET Framework version 2.0 feature) instead of throwing an exception, if your code encounters a situation where it is unsafe to continue executing.

The following program picks a randomly chosen value from a List<T> collection and passes the value to a method. The method tests the parameter for a null value or an empty string. If the method determines it was passed a null value or an empty string, it throws an exception.

.Throw Exception for Invalid Parameter
Throw an Exception for Invalid Parameter

using System;
using System.Collections.Generic;

namespace ArgumentExceptionExample
{
    class Program
    {
        public static void DisplayMotorcycleMake(string motorcycleMake)
        {
            // Check for Null Argument        
            if (motorcycleMake == null)
                throw new ArgumentNullException("motorcycleMake Cannot be Null");

            // Check for Empty Argument        
            if (motorcycleMake.Trim() == String.Empty)
                throw new ArgumentException("motorcycleMake Cannot be Empty");

            // Print the Motorcycle Make
            System.Console.WriteLine("You won a new: {0} ", motorcycleMake);
        }

        static void Main()
        {
            List<string> motorcycleMakes = new List<string>()
            {
                "Triumph",
                "Norton",
                "Harley"
            };

            // Seed Random Number with Time
            Random rnd = new Random((int)DateTime.Now.Ticks);

            // Call Method with a Valid Parameter
            string pickedMotorcycle = motorcycleMakes[rnd.Next(motorcycleMakes.Count)];
            DisplayMotorcycleMake(pickedMotorcycle);

            // Call Method with a No Parameter
            DisplayMotorcycleMake("");   // This causes an Exception
        }
    }
}

Top



Code Contracts

.Code Contracts

Introduced in .NET 4.0 is a data validation technique called Code Contracts. Code Contracts is Microsoft's version of the "Design by Contract" (DbC) methodology first used by Bertrand Meyer with the Eiffel programming language. DbC states that collaboration between two elements of a system must be managed by a set of clauses, which form a contract. Using DbC, methods should explicitly state what they require and what they guarantee if those requirements are met.

Code Contracts contain three clauses:

  1. Preconditions - specifies the obligations of the client (input values).
  2. Postconditions - defines the benefits the supplier delivers to the client (returned values and state conditions).
  3. Invariants - a clause that must hold true at all times, for the lifetime of an instance.
Pros and Cons of Code Contracts

Code Contracts provide greater control over what data, and when the data is validated. It also provides more flexibility on what actions can be taken when invalid data is found (contract violations). These detailed contract specifications can be used by programs, such as PEX when can automate the process of creating test plans. Microsoft Development labs also offers Code Contract Editor Enhancements which allows you to use code contracts with intellisense and Code Contract Tools for performing static and runtime checking of code contracts with the following tools:

  1. Static Checking - static checker can decide if there are any contract violations without even running the program! It checks for implicit contracts, such as null dereferences and array bounds, as well as the explicit contracts.
  2. Runtime Checking - binary rewriter modifies a program by injecting the contracts, which are checked as part of program execution. Rewritten programs improve testability: each contract acts as an oracle, giving a test run a pass/fail indication. Automatic testing tools, such as Pex, take advantage of contracts to generate more meaningful unit tests by filtering out meaningless test arguments that don't satisfy the pre-conditions.
  3. Documentation Generation - documentation generator augments existing XML doc files with contract information. There are also new style sheets that can be used with Sandcastle so that the generated documentation pages have contract sections.

The .NET implementation of code contracts depend upon a binary rewriter which changed the assembly after compilation. While the allows the same syntax for code contracts to be used with all the .NET languages, it slows the build process and complicates services which rely on calling the C# compiler. The enforcement of code contracts also decreases performance. However the binary rewriter can disable some or all of the code contracts in release builds.

Code Contracts Syntax

The pre and post conditions are placed at the top of the method and the binary rewriter moves them to the correct position within the code. An overridden method can not add preconditions. However interfaces and abstract methods can have code contracts. The syntax for code completion preconditions, post conditions, and asserts are shown below.

Precondition
Contract.Requires (parm1 != null);
Contract.Requres (!parm1.IsNullorEmpty(parm1));


Postcondition
Contract.Ensures (list.Contains (item));


Assertion
Contract.Assert ( x == 10);


Converting Traditional Data Checking to Code Contracts

Code contracts also contain an EndContractBlock() method which converts traditional data checking to code contracts. Simply by putting a call to the EndContractBlock() method after the traditional data checking will cause the binary rewriter to convert the code to code contracts. The following program shows the use of the EndContractBlock() method and the results of the code contract checking which are occur during a rebuild of the project. The results are displayed in the output window (ctrl-alt-O) under the Build drop down menu selection.

.Code Contract Build Messages



Code Contract with Contract.EndContractBlock()

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace CodeContractsExample
{
    class Program
    {
        public static void DisplayMotorcycleMake(string motorcycleMake)
        {
            // Check for Null Argument        
            if (motorcycleMake == null)
                throw new ArgumentNullException("motorcycleMake Cannot be Null");

            // Check for Empty Argument        
            if (motorcycleMake.Trim() != String.Empty)
                throw new ArgumentException("motorcycleMake Cannot be Empty");

            // Convert Above Validate to Code Contracts
            Contract.EndContractBlock();

            // Print the Motorcycle Make
            System.Console.WriteLine("You won a new: {0} ", motorcycleMake);
        }

        static void Main()
        {
            List<string> motorcycleMakes = new List<string>()
            {
                "Triumph",
                "Norton",
                "Harley"
            };

            // Seed Random Number with Time
            Random rnd = new Random((int)DateTime.Now.Ticks);

            // Call Method with a Valid Parameter           
            string pickedMotorcycle = motorcycleMakes[rnd.Next(motorcycleMakes.Count)];
            Contract.Assume(!(pickedMotorcycle.Trim() != String.Empty));
            DisplayMotorcycleMake(pickedMotorcycle);
        }
    }
}

Top


Reference Articles

Top