MCAD/MCSD Training Guide (70-316): Error Handling for the User Interface

Date: Mar 21, 2003

Return to the article

This sample chapter covers creating and implementing custom error messages and handlers, along with raising and handling errors. These are Microsoft-specified objectives for the Creating User Services section of the Visual Basic .NET Windows-Based Applications exam.

Objectives

This chapter covers the following Microsoft-specified objectives for the "Creating User Services" section of Exam 70-316, "Developing and Implementing Windows-Based Applications with Microsoft Visual C# .NET and Microsoft Visual Studio .NET":

Implement error handling in the UI.

When you run a Windows application, it may encounter problems that you thought would not occur. For example, the database server is down, a file is missing, or a user has entered improper values. A good Windows application must recover gracefully from this problem rather than abruptly shut down. This exam objective covers the use of exception handling to create robust and fault-tolerant applications. The Microsoft .NET Framework provides some predefined exception classes to help you catch these exceptional situations in your programs. It allows you to create your own exception handling classes and error messages that are specific to your application.

Validate user input.

Garbage in results in garbage out. The best place to avoid incorrect data in an application is at the source—right where the data enters. The Windows Forms library provides an ErrorProvider component that can be used to display helpful error messages and error icons if data that is entered is incorrect. This exam objective covers the ErrorProvider component and various other input-validation techniques.

Outline

Study Strategies

Introduction

The .NET Framework uses the Windows structured exception handling model. Exception handling is an integral part of the .NET Framework that allows the Common Language Runtime (CLR) and your code to throw exceptions across languages and machines. Visual C# .NET helps you fire and handle these exceptions with the help of try, catch, finally, and throw statements. The Framework Class Library (FCL) provides a huge set of exception classes for dealing with various unforeseen conditions in the normal execution environment. If you feel the need to create custom exception classes to meet the specific requirements of an application, you can do so by deriving from the ApplicationException class.

In every program data must be validated before the program can proceed with further processing and storage of the input data. In this chapter I discuss the various techniques you can use to validate data and maintain the integrity of an application. This isn't just a matter of making sure that your application delivers the proper results; if you don't validate input, your application might represent a serious security hole in your systems.

Understanding Exceptions

An exception occurs when a program encounters any unexpected problems such as running out of memory or attempting to read from a file that no longer exists. These problems are not necessarily caused by programming errors but mainly occur due to violations of certain assumptions that are made about the execution environment.

When a program encounters an exception, its default behavior is to throw the exception, which generally translates to abruptly terminating the program after displaying an error message. This is not a characteristic of a robust application and does not make your program popular with users. Your program should be able to handle these exceptional situations and, if possible, gracefully recover from them. This is called exception handling. Proper use of exception handling can make programs robust and easy to develop and maintain. However, if you do not use exception handling properly, you might end up having a program that performs poorly, is harder to maintain, and may potentially mislead its users.

Step by Step 3.1 demonstrates how an exception may occur in a program. Later in this chapter I explain how to handle these exceptions.

STEP BY STEP 3.1 - Exceptions in Windows Applications

  1. Create a new C# Windows application project in the Visual Studio .NET Integrated Development Environment (IDE). Name the project 316C03.

  2. Add a new Windows form to the project. Name it StepByStep3_1.

  3. Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency) and a Button control (btnCalculate) on the form and arrange them as shown in Figure 3.1. Add Label controls as necessary.

Figure 3.1 The mileage efficiency calculator does not implement any error handling for the user interface.

  1. Add the following code to the Click event handler of btnCalculate:

    private void btnCalculate_Click(
      object sender, System.EventArgs e)
    {
      //this code has no error checking. If something
      //goes wrong at run time,
      //it will throw an exception
      decimal decMiles =
        Convert.ToDecimal(txtMiles.Text);
      decimal decGallons =
        Convert.ToDecimal(txtGallons.Text);
      decimal decEfficiency = decMiles/decGallons;
      txtEfficiency.Text =
        String.Format("{0:n}", decEfficiency);

    }

  2. Insert the Main() method to launch the form. Set the form as the startup object for the project.

  3. Run the project. Enter values for miles and gallons and click the Calculate button. The program calculates the mileage efficiency, as expected. Now enter the value 0 in the Gallons of Gas Used field and run the program. The program abruptly terminates after displaying an error message (see Figure 3.2).

Figure 3.2 The development environment gives you a chance to analyze the problem when an exception occurs.

When you run the program created in Step by Step 3.1 from the IDE and the program throws an exception, the IDE gives you options to analyze the problem by debugging the program. In Step by Step 3.1, if you had instead run the program by launching the project's .exe file from Windows Explorer, the program would have terminated after displaying a message box with an error message and some debugging information (see Figure 3.3).

Figure 3.3 When a program is executed outside the Visual Studio .NET environment, debugging information is displayed when an exception is thrown.

From the CLR's point of view, an exception is an object that encapsulates information about the problems that occur during program execution. The FCL provides two categories of exceptions:

Both of these exception classes derive from the base class Exception, which implements the common functionality for exception handling. Neither the ApplicationException class nor the SystemException class adds any new functionality to the Exception class; they exist just to differentiate exceptions in applications from exceptions in the CLR. The classes derived from Exception share some important properties, as listed in Table 3.1

Table 3.1 Important Members of the Exception Class

Member

Type

Description

HelpLink

Property

Specifies the uniform resource locator (URL) of the help file associated with this exception.

InnerException

Property

Specifies an exception associated with this exception. This property is helpful when a series of exceptions are involved. Each new exception can preserve the information about the previous exception by storing it in the InnerException property.

Message

Property

Specifies textual information that indicates the reason for the error and provides possible resolutions.

Source

Property

Specifies the name of the application that causes the error.

StackTrace

Property

Specifies where an error has occurred. If the debugging information is available, the stack trace includes the name of the source file and the program line number.

TargetSite

Property

Represents the method that throws the current exception.


Handling Exceptions

Implement error handling in the user interface:

Abruptly terminating a program when an exception occurs is not a good idea. An application should be able to handle an exception and, if possible, try to recover from it. If recovery is not possible, you can have the program take other steps, such as notify the user and then gracefully terminate the application.

TIP

Floating-Point Types and Exceptions Operations that involve floating-point types never produce exceptions. Instead, in exceptional situations, floating-point operations are evaluated by using the following rules:

If the result of a floating-point operation is too small for the destination format, the result of the operation becomes positive zero or negative zero.

If the result of a floating-point operation is too large for the destination format, the result of the operation becomes positive infinity or negative infinity.

If a floating-point operation is invalid, the result of the operation becomes NaN (not a number).

The .NET Framework allows exception handling to interoperate among languages and across machines. You can catch exceptions thrown by code written in one .NET language in a different .NET language. The .NET framework also allows you to handle exceptions thrown by legacy Component Object Model (COM) applications and legacy non-COM Windows applications.

Exception handling is such an integral part of the .NET framework that when you look for a method reference in the product documentation, there is always a section that specifies what exceptions a call to that method might throw.

You can handle exceptions in Visual C# .NET programs by using a combination of exception handling statements: try, catch, finally, and throw.

The try Block

You should place the code that might cause exceptions in a try block. A typical try block looks like this:

try
{
  //code that may cause exception
}

You can place any valid C# statements inside a try block, including another try block or a call to a method that places some of its statements inside a try block. The point is, at runtime you may have a hierarchy of try blocks placed inside each other. When an exception occurs at any point, rather than executing any further lines of code, the CLR searches for the nearest try block that encloses this code. The control is then passed to a matching catch block (if any) and then to the finally block associated with this try block.

A try block cannot exist on its own; it must be immediately followed by either one or more catch blocks or a finally block.

The catch Block

You can have several catch blocks immediately following a try block. Each catch block handles an exception of a particular type. When an exception occurs in a statement placed inside the try block, the CLR looks for a matching catch block that is capable of handling that type of exception. A typical try-catch block looks like this:

try
{
  //code that may cause exception
}
catch(ExceptionTypeA)
{
  //Statements to handle errors occurring
  //in the associated try block
}
catch(ExceptionTypeB)
{
  //Statements to handle errors occurring
  //in the associated try block
}

The formula the CLR uses to match the exception is simple: While matching it looks for the first catch block with either the exact same exception or any of the exception's base classes. For example, a DivideByZeroException exception would match any of these exceptions: DivideByZeroException, ArithmeticException, SystemException, and Exception. In the case of multiple catch blocks, only the first matching catch block is executed. All other catch blocks are ignored.

When you write multiple catch blocks, you need to arrange them from specific exception types to more general exception types. For example, the catch block for catching a DivideByZeroException exception should always precede the catch block for catching a ArithmeticException exception. This is because the DivideByZeroException exception derives from ArithmeticException and is therefore more specific than the latter. The compiler flags an error if you do not follow this rule.

NOTE

Exception Handling Hierarchy If there is no matching catch block, an unhandled exception results. The unhandled exception is propagated back to its caller code. If the exception is not handled there, it propagates further up the hierarchy of method calls. If the exception is not handled anywhere, it goes to the CLR, whose default behavior is to terminate the program immediately.

A try block need not necessarily have a catch block associated with it, but if it does not, it must have a finally block associated with it.

STEP BY STEP 3.2 - Handling Exceptions

  1. Add a new Windows form to the project. Name it StepByStep3_2.

  2. Create a form similar to the one created in Step by Step 3.1 (refer to Figure 3.1), with the same names for the controls.

  3. Add the following code to the Click event handler of btnCalculate:

    private void btnCalculate_Click(
      object sender, System.EventArgs e)
    {
      //put all the code that may require graceful
      //error recovery in a try block
      try
      {
        decimal decMiles =
          Convert.ToDecimal(txtMiles.Text);
        decimal decGallons =
          Convert.ToDecimal(txtGallons.Text);
        decimal decEfficiency = decMiles/decGallons;
        txtEfficiency.Text =
          String.Format("{0:n}", decEfficiency);
      }
      // try block should at least have one catch or a
      // finally block. catch block should be in order
      // of specific to the generalized exceptions
      // otherwise compilation generates an error
      catch (FormatException fe)
      {
        string msg = String.Format(
           "Message: {0}\n Stack Trace:\n {1}",
           fe.Message, fe.StackTrace);
        MessageBox.Show(msg, fe.GetType().ToString());
      }
      catch (DivideByZeroException dbze)
      {
        string msg = String.Format(
           "Message: {0}\n Stack Trace:\n {1}",
           dbze.Message, dbze.StackTrace);
        MessageBox.Show(
          msg, dbze.GetType().ToString());
      }
      //catches all CLS-compliant exceptions
      catch(Exception ex)
      {
        string msg = String.Format(
          "Message: {0}\n Stack Trace:\n {1}",
          ex.Message, ex.StackTrace);
        MessageBox.Show(msg, ex.GetType().ToString());
      }
      //catches all other exceptions including
      //the NON-CLS compliant exceptions
      catch
      {
        //just rethrow the exception to the caller
        throw;
      }

    }

TIP

CLS- and Non-CLS-Compliant Exceptions All languages that follow the Common Language Specification (CLS) throw exceptions of type System.Exception or a type that derives from System.Exception. A non-CLS-compliant language may throw exceptions of other types, too. You can catch those types of exceptions by placing a general catch block (that does not specify any exception) with a try block. In fact, a general catch block can catch exceptions of all types, so it is the most generic of all catch blocks and should be the last catch block among the multiple catch blocks associated with a try block.

  1. Insert the Main() method to launch the form. Set the form as the startup object for the project.

  2. Run the project. Enter values for miles and gallons and click the Calculate button. The program calculates the mileage efficiency, as expected. Now enter the value 0 in the Gallons of gas used field and run the program. Instead of abruptly terminating as in the earlier case, the program shows a message about the DivideByZeroException exception, as shown in Figure 3.4, and it continues running. Now enter some alphabetic characters instead of number in the fields and click the Calculate button. This time you get a FormatException exception, and the program continues to run. Now try entering very large values in both the fields. If the values are large enough, the program encounters an OverflowException exception, but because the program is catching all types of exceptions, it continues running.

Figure 3.4 To get information about an exception, you can catch the Exception object and access its Message property.

NOTE

checked and unchecked Visual C# .NET provides the checked and unchecked keywords, which can be used to enclose a block of statements (for example, checked {a = c/d}) or as an operator when you supply parameters enclosed in parentheses (for example, unchecked(c/d)). The checked keyword enforces checking of any arithmetic operation for overflow exceptions. If constant values are involved, they are checked for overflow at compile time. The unchecked keyword suppresses the overflow checking and instead of raising an OverflowException exception, the unchecked keyword returns a truncated value in case of overflow.

If checked and unchecked are not used, the default behavior in C# is to raise an exception in case of overflow for a constant expression or truncate the results in case of overflow for the nonconstant expressions.

The program in Step by Step 3.2 displays a message box when an exception occurs; the StackTrace property lists the methods in the reverse order of their calling sequence. This helps you understand the logical flow of the program. You can also place any appropriate error handling code in place, and you can display a message box.

When you write a catch block that catches exceptions of type Exception, the program catches all CLS-compliant exceptions. This includes all exceptions, unless you are interacting with legacy COM or Windows 32-bit Application Programming Interface (Win32 API) code. If you want to catch all kinds of exceptions, whether CLS-compliant or not, you can place a catch block with no specific type. A catch block like this must be the last catch block in the list of catch blocks because it is the most generic one.

NOTE

Do Not Use Exceptions to Control the Normal Flow of Execution Using exceptions to control the normal flow of execution can make your code difficult to read and maintain because the use of try-catch blocks to deal with exceptions forces you to fork the regular program logic between two separate locations—the try block and the catch block.

You might be thinking that it is a good idea to catch all sorts of exceptions in your code and suppress them as soon as possible. But it is not a good idea. A good programmer catches an exception in code only if he or she can answer yes to one or more of the following questions:

If you answer no to all these questions, then you should not catch the exception but rather just let it go. In that case, the exception propagates up to the calling code, and the calling code might have a better idea of how to handle the exception.

The throw Statement

A throw statement explicitly generates an exception in code. You use throw when a particular path in code results in an anomalous situation.

You should not throw exceptions for anticipated cases such as the user entering an invalid username or password; instead, you can handle this in a method that returns a value indicating whether the login is successful. If you do not have the correct permissions to read records from the user table and you try to read those records, an exception is likely to occur because a method for validating users should normally have read access to the user table.

WARNING

Use throw Only When Required The throw statement is an expensive operation. Use of throw consumes significant system resources compared to just returning a value from a method. You should use the throw statement cautiously and only when necessary because it has the potential to make your programs slow.

There are two ways you can use the throw statement. In its simplest form, you can just rethrow the exception in a catch block:

catch(Exception e)
{
  //TODO: Add code to create an entry in event log
  throw;
}

This usage of the throw statement rethrows the exception that was just caught. It can be useful in situations in which you don't want to handle the exception yourself but would like to take other actions (for example, recording the error in an event log, sending an email notification about the error) when an exception occurs and then pass the exception as-is to its caller.

The second way to use the throw statement is to use it to throw explicitly created exceptions, as in this example:

string strMessage =
  "EndDate should be greater than the StartDate";
ArgumentOutOfRangeException newException =
  new ArgumentOutOfRangeException(strMessage);
throw newException;

In this example, I first create a new instance of the ArgumentOutOfRangeException object and associate a custom error message with it, and then I throw the newly created exception.

You are not required to put this usage of the throw statement inside a catch block because you are just creating and throwing a new exception rather than rethrowing an existing one. You typically use this technique in raising your own custom exceptions. I discuss how to do that later in this chapter.

Another way of throwing an exception is to throw it after wrapping it with additional useful information, as in this example:

catch(ArgumentNullException ane)
{
  //TODO: Add code to create an entry in the log file
  string strMessage = "CustomerID cannot be null";
  ArgumentNullException newException =
    new ArgumentNullException(strMessage, ane);
  throw newException;
} 

TIP

Custom Error Messages When you create an exception object, you should use its constructor that allows you to associate a custom error message rather than use its default constructor. The custom error message can pass specific information about the cause of the error and a possible way to resolve it.

Many times, you need to catch an exception that you cannot handle completely. In such a case you should perform any required processing and throw a more relevant and informative exception to the caller code so that it can perform the rest of the processing. In this case, you can create a new exception whose constructor wraps the previously caught exception in the new exception's InnerException property. The caller code then has more information available to handle the exception appropriately.

It is interesting to note that because InnerException is of type Exception, it also has an InnerException property that may store a reference to another exception object. Therefore, when you throw an exception that stores a reference to another exception in its InnerException property, you are actually propagating a chain of exceptions. This information is very valuable at the time of debugging and allows you to trace the path of a problem to its origin.

The finally Block

The finally block contains code that always executes, whether or not any exception occurs. You use the finally block to write cleanup code that maintains your application in a consistent state and preserves sanitation in the environment. For example, you can write code to close files, database connections, and related input/output resources in a finally block.

TIP

No Code in Between try-catch-finally Blocks When you write try, catch, and finally blocks, they should be in immediate succession of each other. You cannot write any other code between the blocks, although compilers allow you to place comments between them.

It is not necessary for a try block to have an associated finally block. However, if you do write a finally block, you cannot have more than one, and the finally block must appear after all the catch blocks.

Step by Step 3.3 illustrates the use of the finally block.

STEP BY STEP 3.3 - Using the finally Block

  1. Add a new Windows form to the project. Name it StepByStep3_3.

  2. Place two TextBox controls (txtFileName and txtText), two Label controls (keep their default names), and a Button control (btnSave) on the form and arrange them as shown in Figure 3.5.

Figure 3.5 When you click the Save button, the code in finally block executes, regardless of any exception in the try block.

  1. Attach the Click event handler to the btnSave control and add the following code to handle the Click event:

    private void btnSave_Click(
      object sender, System.EventArgs e)
    {
      // a StreamWriter writes characters to a stream
      StreamWriter sw = null;
      try
      {
        sw = new StreamWriter(txtFileName.Text);
        // Attempt to write the text box
        // contents in a file
        foreach(string line in txtText.Lines)
          sw.WriteLine(line);
        // This line only executes if there
        // were no exceptions so far
        MessageBox.Show(
         "Contents written, without any exceptions");
      }
      //catches all CLS-compliant exceptions
      catch(Exception ex)
      {
        string msg = String.Format(
          "Message: {0}\n Stack Trace:\n {1}",
          ex.Message, ex.StackTrace);
        MessageBox.Show(msg, ex.GetType().ToString());
        goto end;
      }
      // finally block is always executed to make sure
      // that the resources get closed whether or not
      // the exception occurs. Even if there is a goto
      // statement in catch or try block the final block
      // is first executed before the control goes to
      // the goto label
      finally
      {
        if (sw != null)
          sw.Close();
        MessageBox.Show("Finally block always " +
         "executes whether or not exception occurs");
      }
    end:
      MessageBox.Show("Control is at label: end");

    }

  2. Insert a Main() method to launch the form. Set the form as the startup object for the project.

  3. Run the project. You should see a Windows form, as shown in Figure 3.5. Enter a filename and some text. Watch the order of messages. Note that the message box being displayed in the finally block is always displayed prior to the message box displayed by the end label.

TIP

The finally Block Always Executes If you have a finally block associated with a try block, the code in the finally block always executes, whether or not an exception occurs.

Step by Step 3.3 illustrates that the finally block always executes. In addition, if there is a transfer-control statement such as goto, break, or continue in either the try block or the catch block, the control transfer happens after the code in the finally block is executed. What happens if there is a transfer-control statement in the finally block also? That is not an issue because the C# compiler does not allow you to put a transfer-control statement such as goto inside a finally block.

One of the ways the finally statement can be used is in the form of a try-finally block without any catch block. Here is an example:

try
{
  //Write code to allocate some resources
}
finally
{
  //Write code to Dispose all allocated resources
}

NOTE

Throwing Exceptions from the finally Block Although it is perfectly legitimate to throw exceptions from a finally block, it is not recommended. The reason for this is that when you are processing a finally block, you might already have an unhandled exception waiting to be caught.

This use ensures that allocated resources are properly disposed of, no matter what. In fact, C# provides a using statement that does the exact same job but with less code. A typical use of the using statement is as follows:

// Write code to allocate some resource. List the
// allocate resources in a comma-separated list inside
// the parentheses, with the following using block
using(...)
{
 //use the allocated resource
}
// Here, the Dispose() method is called for all the
// objects referenced in the parentheses of the
// using statement. There is no need to write
// any additional code

Creating and Using Custom Exceptions

Implement error handling in the user interface.

The exception classes provided by the .NET Framework, combined with the custom messages created when you create a new Exception object to throw or rethrow exceptions, should suffice for most of your exception handling requirements. In some cases, however, you might need exception types that are specific to the problem you are solving.

TIP

Using ApplicationException as a Base Class for Custom Exceptions Although you can derive custom exception classes directly from the Exception class, Microsoft recommends that you derive custom exception classes from the ApplicationException class.

The .NET Framework allows you to define custom exception classes. To keep your custom-defined Exception class homogeneous with the .NET exception framework, Microsoft recommends that you consider the following when you design a custom exception class:

Step by Step 3.4 shows you how to create a custom exception.

STEP BY STEP 3.4 - Creating and Using a Custom Exception

  1. Add a new Windows form to the project. Name it StepByStep3_4.

  2. Place and arrange controls on the form as shown in Figure 3.6. Name the TextBox control txtDate, the Button control btnIsLeap, and the Label control inside the Results panel lblResult.

Figure 3.6 The leap year finder implements a custom exception for an invalid date format.

  1. Switch to the code view and add the following definition for the MyOwnInvalidDateFormatException class to the end of the class definition for project StepByStep3_4:

    // You can create your own exception classes by
    // deriving from the ApplicationException class.
    // It is good coding practice to end the class name
    // of the custom exception with the word "Exception"
    public class MyOwnInvalidDateFormatException :
      ApplicationException
    {
      // It is a good practice to implement the three
      // recommended common constructors as shown here.
      public MyOwnInvalidDateFormatException()
      {
      }
      public MyOwnInvalidDateFormatException(
        string message): base(message)
      {
        this.HelpLink =
       "file://MyOwnInvalidDateFormatExceptionHelp.htm";
      }
      public MyOwnInvalidDateFormatException(
       string message, Exception inner) :
       base(message, inner)
      {
      }

    }

  2. Add the following definition for the Date class:

    //This class does elementary date handling required
    //for this program
    public class Date
    {
      private int day, month, year;
    
      public Date(string strDate)
      {
        if (strDate.Trim().Length == 10)
        {
          //Input data might be in an invalid format
          //In which case, Convert.ToDateTime()
          // method will fail
          try
          {
            DateTime dt =
              Convert.ToDateTime(strDate);
            day = dt.Day;
            month = dt.Month;
            year = dt.Year;
          }
          //Catch the exception, attach that to the
          //custom exception and
          //throw the custom exception
          catch(Exception e)
          {
            throw new
             MyOwnInvalidDateFormatException(
             "Custom Exception Says: " +
             "Invalid Date Format", e);
          }
        }
        else
          //Throw the custom exception
          throw new MyOwnInvalidDateFormatException(
            "The input does not match the " +
            "required format: MM/DD/YYYY");
      }
    
      //Find if the given date belongs to a leap year
      public bool IsLeapYear()
      {
        return (year%4==0) && ((year %100 !=0) ||
          (year %400 ==0));
      }

    }

  3. Add the following event handler for the Click event of btnIsLeap:

    private void btnIsLeap_Click(
       object sender, System.EventArgs e)
    {
      try
      {
        Date dt = new Date(txtDate.Text);
        if (dt.IsLeapYear())
          lblResult.Text =
            "This date is in a leap year";
        else
          lblResult.Text =
            "This date is NOT in a leap year";
      }
      //Catch the custom exception and
      //display an appropriate message
      catch (MyOwnInvalidDateFormatException dte)
      {
        string msg;
        //If some other exception was also
        //attached with this exception
        if (dte.InnerException != null)
         msg = String.Format(
         "Message:\n {0}\n\n Inner Exception:\n {1}",
         dte.Message, dte.InnerException.Message);
        else
          msg = String.Format(
            "Message:\n {0}\n\n Help Link:\n {1}",
            dte.Message, dte.HelpLink);
    
        MessageBox.Show(msg, dte.GetType().ToString());
      }

    }

  4. Insert a Main() method to launch the form. Set the form as the startup object for the project.

  5. Run the project. Enter a date and click the button. If the date you enter is in the required format, you see a result displayed in the Results group box; otherwise, you get a message box showing the custom error message thrown by the custom exception, as in Figure 3.7.

Figure 3.7 You can associate a customized error message and a help link with a custom exception.

Guided Practice Exercise 3.1

You are a Windows developer for a data analysis company. For one of your applications you need to create a keyword searching form that asks for a filename and a keyword from the user (as shown in Figure 3.8). The form should search for the keyword in the file and display the number of lines that contain the keyword in the results group box. Your form assumes that the entered keyword is a single word. If it is not a single word, you need to create and throw a custom exception for that case.

Figure 3.8 The keyword searching form throws a custom exception if the input is not in the required format.

How would you throw a custom exception to implement custom error messages and custom error handling in your program?

You should try working through this problem on your own first. If you get stuck, or if you'd like to see one possible solution, follow these steps:

  1. Add a new form to your Visual C# .NET project. Name the form GuidedPracticeExercise3_1.cs.

  2. Place and arrange controls on the form as shown in Figure 3.8. Name the TextBox control for accepting the filename txtFileName and the Browse control btnBrowse. Set the ReadOnly property of txtFileName to true. Name the TextBox control for accepting the keyword txtKeyword and the Button control btnSearch. Set the tab order of the form in the correct order, to ensure that the user's cursor is not placed in the read-only text box when the application starts.

  3. Add an OpenFileDialog control to the form and change its name to dlgOpenFile.

  4. Create a new class named BadKeywordFormatException that derives from ApplicationException and place the following code in it:

    public class BadKeywordFormatException :
      ApplicationException
    {
      public BadKeywordFormatException()
      {
      }
      public BadKeywordFormatException(string message):
        base(message)
      {
      }
      public BadKeywordFormatException(string message,
        Exception inner): base(message, inner)
      {
      }
    }
  5. Create a method named GetKeywordFrequency() in the GuidedPracticeExercise3_1 class. This method should accept a string and return the number of lines that contain the string. Add the following code to the method:

    private int GetKeywordFrequency(string path)
    {
      if(this.txtKeyword.Text.Trim().IndexOf(' ') >= 0)
        throw new BadKeywordFormatException(
         "The keyword must only have a single word");
    
      int count = 0;
      if (File.Exists(path))
      {
        StreamReader sr =
          new StreamReader(txtFileName.Text);
        while (sr.Peek() > -1)
         if (sr.ReadLine().IndexOf(txtKeyword.Text)
          >= 0)
            count++;
      }
      return count;
    }
  6. Add the following code to the Click event handler of btnBrowse:

    private void btnBrowse_Click(
      object sender, System.EventArgs e)
    {
      if (dlgOpenFile.ShowDialog() == DialogResult.OK)
        txtFileName.Text = dlgOpenFile.FileName;
    }
  7. Add the following code to the Click event handler of btnSearch:

    private void btnSearch_Click(
      object sender, System.EventArgs e)
    {
      if (txtKeyword.Text.Trim().Length == 0)
      {
        MessageBox.Show(
          "Please enter a keyword to search for",
          "Missing Keyword");
        return;
      }
      try
      {
        lblResult.Text = String.Format(
         "The keyword: '{0}', was found in {1} lines",
         txtKeyword.Text,
         GetKeywordFrequency(txtFileName.Text));
      }
      catch(BadKeywordFormatException bkfe)
      {
        string msg = String.Format(
          "Message:\n {0}\n\n StackTrace:\n{1}",
          bkfe.Message, bkfe.StackTrace);
        MessageBox.Show(msg, bkfe.GetType().ToString());
      }
    }
  8. Insert the Main() method to launch the form GuidedPracticeExercise3_1.cs. Set the form as the startup object for the project.

  9. Run the project. Click the Browse button and select an existing file. Enter the keyword to search for in the file and click the Search button. If the keyword entered is in the wrong format (for example, if it contains two words), the custom exception is raised.

If you have difficulty following this exercise, review the sections "Handling Exceptions" and "Creating and Using Custom Exceptions" earlier in this chapter. After reviewing, try this exercise again.

Managing Unhandled Exceptions

The CLR-managed applications execute in an isolated environment called an application domain. The AppDomain class of the System namespace programmatically represents the application domain. The AppDomain class provides a set of events that allows you to respond when an assembly is loaded, when an application domain is unloaded, or when an application throws an unhandled exception. In this chapter, we are particularly interested in the UnhandledException event of the AppDomain class, which occurs when any other exception handler does not catch an exception. Table 3.2 lists the properties of the UnhandledExceptionEventArgs class.

Table 3.2 Important Members of the UnhandledExceptionEventArgs Class

Member

Type

Description

ExceptionObject

Property

Specifies the unhandled exception object that corresponds to the current domain

IsTerminating

Property

Indicates whether the CLR is terminating


You can attach an event handler with the UnhandledException event to take custom actions such as logging exception-related information. A log that is maintained over a period of time may help you find and analyze patterns with useful debugging information. There are several ways you can log information that is related to an event:

Among these ways, the Windows event log offers the most robust method for event logging because it requires minimal assumptions for logging events. The other cases are not as fail-safe; for example, an application could loose connectivity with the database or with the SMTP server, or you might have problems writing an entry in a custom log file.

The .NET Framework provides you access to the Windows event log through the EventLog class. Windows 2000 and later have three default logs—application, system, and security. You can use the EventLog class to create custom event logs. You can easily view the event log by using the Windows Event Viewer utility.

NOTE

When Not to Use the Windows Event Log The Windows event log is not available on older versions of Windows, such as Windows 98. If your application needs to support computers running older versions of Windows, you might want to create a custom error log. In a distributed application, you might want to log all events centrally in a SQL Server database. To make the scheme fail-safe, you can choose to log locally if the database is not available and transfer the log to the central database when it is available again.

You should familiarize yourself with the important members of the EventLog class that are listed in Table 3.3.

Table 3.3 Important Members of the EventLog Class

Member

Type

Description

Clear()

Method

Removes all entries from the event log and makes it empty

CreateEventSource()

Method

Creates an event source that you can use to write to a standard or custom event log

Entries

Property

Gets the contents of the event log

Log

Property

Specifies the name of the log to read from or write to

MachineName

Property

Specifies the name of the computer on which to read or write events

Source

Property

Specifies the event source name to register and use when writing to the event log

SourceExists()

Method

Specifies whether the event source exists on a computer

WriteEntry()

Method

Writes an entry in the event log


STEP BY STEP 3.5 - Logging Unhandled Exceptions in the Windows Event Log

  1. Add a new Windows form to the project. Name it StepByStep3_5.

  2. Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency) and a Button control (btnCalculate) on the form and arrange them as shown in Figure 3.1. Add Label controls as necessary.

  3. Switch to the code view and add the following using directive at the top of the program:

    using System.Diagnostics;
  4. Double-click the Button control and add the following code to handle the Click event handler of the Button control:

    private void btnCalculate_Click(
      object sender, System.EventArgs e)
    {
      //This code has no error checking.
      //If something goes wrong at run time,
      //it will throw an exception
      decimal decMiles =
        Convert.ToDecimal(txtMiles.Text);
      decimal decGallons =
        Convert.ToDecimal(txtGallons.Text);
      decimal decEfficiency = decMiles/decGallons;
      txtEfficiency.Text =
        String.Format("{0:n}", decEfficiency);

    }

  5. Add the following code in the class definition:

    private static void UnhandledExceptionHandler(
      object sender, UnhandledExceptionEventArgs ue)
    {
      Exception unhandledException =
        (Exception) ue.ExceptionObject;
    
      //If no event source exist,
      //create an event source.
      if(!EventLog.SourceExists(
         "Mileage Efficiency Calculator"))
      {
        EventLog.CreateEventSource(
          "Mileage Efficiency Calculator",
          "Mileage Efficiency Calculator Log");
      }
    
      // Create an EventLog instance
      // and assign its source.
      EventLog eventLog = new EventLog();
      eventLog.Source = "Mileage Efficiency Calculator";
    
      // Write an informational entry to the event log.
      eventLog.WriteEntry(unhandledException.Message);
      MessageBox.Show("An exception occurred: " +
        "Created an entry in the log file");

    }

  6. Insert the following Main() method:

    [STAThread]
    public static void Main()
    {
      // Create an AppDomain object
      AppDomain adCurrent = AppDomain.CurrentDomain;
      // Attach the UnhandledExceptionEventHandler to
      // the UnhandledException of the AppDomain object
      adCurrent.UnhandledException += new
       UnhandledExceptionEventHandler(
       UnhandledExceptionHandler);
      Application.Run(new StepByStep3_5());

    }

  7. Set the form as the startup object for the project.

  8. Run the project. Enter invalid values for miles and gallons and run the program. When an unhandled exception occurs, a message box is displayed, notifying you that the exception has been logged. You can view the logged message by selecting Event Viewer from the Administrative Tools section of the Control Panel. The Event Viewer displays the Mileage Efficiency Calculator Log and other logs in the left pane (see Figure 3.9). The right pane of the Event Viewer shows the events that are logged. You can double-click an event to view the description and other properties of the event, as shown in Figure 3.10.

Figure 3.9 You can view messages logged to an event log by using the Windows Event Viewer.

Figure 3.10 You can view event properties for a particular event to see the event-related details.

Validating User Input

Validate user input.

Garbage in results in garbage out. When designing an application that accepts data from the user, you must ensure that the entered data is acceptable for the application. The most relevant place to ensure the validity of data is at the time of data entry itself. You can use various techniques for validating data:

The first technique is discussed relative to the use of various controls in Chapter 2, "Controls"; the following sections cover rest of these techniques.

Keystroke-Level Validation

When you press a key on a control, three events take place, in the following order:

  1. KeyDown

  2. KeyPress

  3. KeyUp

You can program the event handlers for these events in order to perform keystroke-level validation. You choose the event to program based on the order in which the event is fired and the information that is passed in the event argument of the event handler.

The KeyPress event happens after the KeyDown event but before the KeyUp event. Its event handler receives an argument of type KeyPressEventArgs. Table 3.4 lists the properties of KeyPressEventArgs.

Table 3.4 Important Members of the KeyPressEventArgs Class

Member

Type

Description

Handled

Property

Indicates whether the event has been handled

KeyChar

Property

Returns the character value that corresponds to the key


The KeyPress event fires only if the key that is pressed generates a character value. To handle keypresses for function keys, control keys, and cursor movement keys, you must use the KeyDown and KeyUp events.

The KeyDown and KeyUp events occur when the user presses and releases a key on the keyboard, respectively. Event handlers of these events receive an argument of KeyEventArgs type; it provides the properties listed in Table 3.5.

Table 3.5 Important Members of the KeyEventArgs Class

Member

Type

Description

Alt

Property

Returns true if the Alt key is pressed; otherwise, returns false.

Control

Property

Returns true if the Ctrl key is pressed; otherwise, returns false.

Handled

Property

Indicates whether the event has been handled.

KeyCode

Property

Returns the keyboard code for the event. Its value is one of the values specified in the Keys enumeration.

KeyData

Property

Returns the key code for the pressed key, along with modifier flags that indicate what combination of modifier keys (Ctrl, Shift, and Alt) are pressed at the same time.

KeyValue

Property

Returns the integer representation of the KeyData property.

Modifiers

Property

Returns the modifier flags that indicate what combination of modifier keys (Ctrl, Shift, and Alt) are pressed.

Shift

Property

Returns true if the Shift key is pressed; otherwise, returns false.


The KeyPreview Property

By default, only the active control receives the keystroke events. The Form object also has the KeyPress, KeyUp, and KeyDown events, but they are fired only when all the controls on the form are either hidden or disabled.

When you set the KeyPreview property of a form to true, the form receives all three events—KeyPress, KeyUp, and KeyDown—just before the active control receives these events. This allows you to set up a two-tier validation on controls. If you want to discard certain types of characters at the form level, you can set the Handled property for the event argument to true (this does not allow the event to propagate to the active control); otherwise, the events propagate to the active control. You can then use keystroke events at the control level to perform field-specific validations, such as restricting the field to only numeric digits.

Field-Level Validation

Field-level validation ensures that the value entered in the field is in accordance with the application's requirements. If it is not, you can display an error to alert the user about the problem. These are appropriate reasons to perform field-level validations:

When the user enters and leaves a field, the events occur in the following order:

  1. Enter (Occurs when a control is entered.)

  2. GotFocus (Occurs when a control receives focus.)

  3. Leave (Occurs when focus leaves a control.)

  4. Validating (Occurs when a control is validating.)

  5. Validated (Occurs when a control is finished validating.)

  6. LostFocus (Occurs when a control looses focus.)

The Validating event is the ideal place to store the validating logic for a field. The following sections explain the use of the Validating event and the CausesValidation property for field-level validation. They also discuss the use of the ErrorProvider component to display error messages to the user.

The Validating Event

The Validating event is the ideal place for storing the field-level validation logic for a control. The event handler for validating the event receives an argument of type CancelEventArgs. Its only property, Cancel, cancels the event when it is set to true.

NOTE

The Validating Event and Sticky Forms The Validating event fires when you close a form. If inside the Validating event you set the Cancel property of the CancelEventArgs argument to true, the Validating event also cancels the close operation.

There is a workaround for this problem. Inside the Validating event, you should set the Cancel property of the CancelEventArgs argument to true if the mouse is in the form's client area. The close button is in the title bar that is outside the client area of the form. Therefore, when the user clicks the close button, the Cancel property is not set to true.

Inside the Validating event, you can write code to do the following:

Inside the Validating event, you might also want to retain the focus in the current control, thus forcing the user to fix the problem before proceeding further. To do this, you can use either of the following techniques:

A related event, Validated, is fired just after the Validating event occurs—and it enables you to take actions after the control's contents have been validated.

The CausesValidation Property

When you use the Validating event to restrict the focus in the control by canceling the event, you must also consider that you are making the control sticky.

Consider a case in which the user is currently on a control such as a TextBox control, with incorrect data, and you are forcing the user to fix the problem before leaving the control, by setting the Cancel property of CancelEventArgs to true. When the user clicks the Help button in the toolbar to check what is wrong, nothing happens unless the user makes a correct entry. This can be an annoying situation for the user, so you want to avoid it in your applications.

The CausesValidation property comes to your rescue in such a case. The default value of the CausesValidation property for a control is true for all controls, which means that the Validating event fires for any control, requiring validation before the control in question receives the focus.

When you want a control to respond, regardless of the validation status of other controls, you should set the CausesValidation property of that control to false. For example, in the previous example, the Help button in the toolbar would be set with the CausesValidation property set to false.

The ErrorProvider Component

The ErrorProvider component in the Visual Studio .NET toolbox is useful when you're showing validation-related error messages to the user. The ErrorProvider component can set a small icon next to a field when it contains an error. When the user moves the mouse pointer over the icon, an error message pops up as a ToolTip. This is a better way of displaying error messages than the old way of using message boxes because it eliminates at least two serious problems with message boxes:

Table 3.6 lists some important members of the ErrorProvider class with which you should familiarize yourself.

Table 3.6 Important Members of the ErrorProvider Class

Member

Type

Description

BlinkRate

Property

Specifies the rate at which the error icon flashes.

BlinkStyle

Property

Specifies a value that indicates when the error icon flashes.

ContainerControl

Property

Specifies the component's parent control.

GetError()

Method

Returns the error description string for the specified control.

Icon

Property

Specifies an icon to display next to a control. The icon is displayed only when an error description string has been set for the control.

SetError()

Method

Sets the error description string for the specified control.

SetIconAlignment()

Method

Sets the location at which to place an error icon with respect to the control. It has one of the ErrorIconAlignment values (BottomLeft, BottomRight, MiddleLeft, MiddleRight, TopLeft, and TopRight).

SetIconPadding()

Method

Specifies the amount of extra space to leave between the control and the error icon.


The ErrorProvider component displays an error icon next to a field, based on the error message string. The error message string is set by the SetError() method. If the error message is empty, no error icon is displayed, and the field is considered correct. Step by Step 3.5 shows how to use the ErrorProvider component.

STEP BY STEP 3.6 - Using the ErrorProvider Component and Other Validation Techniques

  1. Add a new Windows form to the project. Name it StepByStep3_6.

  2. Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency) and a Button control (btnCalculate) on the form and arrange them as shown in Figure 3.11. Add Label controls as necessary.

  3. The ErrorProvider component is present in the Windows Forms tab of the Visual Studio .NET toolbox. Add an ErrorProvider component (errorProvider1) to the form. The ErrorProvider component is placed in the component tray.

  4. Double-click the form and add the following code to handle the Load event handler of the Form control:

    private void StepByStep3_6_Load(
      object sender, System.EventArgs e)
    {
      // Set the ErrorProvider's Icon
      // alignment for the TextBox controls
      errorProvider1.SetIconAlignment(
        txtMiles, ErrorIconAlignment.MiddleLeft);
      errorProvider1.SetIconAlignment(
        txtGallons, ErrorIconAlignment.MiddleLeft);

    }

  5. Attach the Validating event handlers to the TextBox controls and add the following code to handle the Validating event handler of the txtMiles and txtGallons controls:

    private void txtMiles_Validating(object sender,
      System.ComponentModel.CancelEventArgs e)
    {
      try
      {
        decimal decMiles =
          Convert.ToDecimal(txtMiles.Text);
        errorProvider1.SetError(txtMiles, "");
      }
      catch(Exception ex)
      {
        errorProvider1.SetError(txtMiles, ex.Message);
      }
    }
    
    private void txtGallons_Validating(object sender,
       System.ComponentModel.CancelEventArgs e)
    {
      try
      {
        decimal decGallons =
           Convert.ToDecimal(txtGallons.Text);
        if (decGallons > 0)
          errorProvider1.SetError(txtGallons, "");
        else
          errorProvider1.SetError(txtGallons,
           "Please enter a value > 0");
      }
      catch(Exception ex)
      {
        errorProvider1.SetError(
          txtGallons, ex.Message);
      }
    }
  6. Add the following code to the Click event handler of btnCalculate:

    private void btnCalculate_Click(
      object sender, System.EventArgs e)
    {
      // Check whether the error description is not empty
      // for either of the TextBox controls
      if (errorProvider1.GetError(txtMiles) != "" ||
        errorProvider1.GetError(txtGallons) != "")
        return;
    
      try
      {
        decimal decMiles =
          Convert.ToDecimal(txtMiles.Text);
        decimal decGallons =
          Convert.ToDecimal(txtGallons.Text);
        decimal decEfficiency = decMiles/decGallons;
        txtEfficiency.Text =
          String.Format("{0:n}", decEfficiency);
      }
      catch(Exception ex)
      {
        string msg = String.Format(
          "Message: {0}\n Stack Trace:\n {1}",
          ex.Message, ex.StackTrace);
        MessageBox.Show(msg, ex.GetType().ToString());
      }

    }

  7. Insert the Main() method to launch the form. Set the form as the startup object for the project.

  8. Run the project. Enter values for miles and gallons and click Calculate. The program calculates the mileage efficiency, as expected. When you enter an invalid value into any of the TextBox controls, the error icon starts blinking and displays the error message when the mouse is hovered over the error icon, as shown in Figure 3.11.

Figure 3.11 The ErrorProvider component shows the error icon and the error message in a nonintrusive way.

Enabling Controls Based on Input

One of the useful techniques for restricting user input is selectively enabling and disabling controls. These are some common cases in which you would want to do this:

The Enabled property for a control is true by default. When you set it to false, the control cannot receive the focus and appears grayed out.

For a control such as TextBox, you can also use the ReadOnly property to restrict user input. One advantage of using the ReadOnly property is that the control is still able to receive focus, so you are able to scroll through any text in the control that is not initially visible. In addition, you can select and copy the text to the Clipboard, even if the ReadOnly property is true.

Other Properties for Validation

In addition to the techniques mentioned in the preceding sections, the properties described in the following sections allow you to enforce some restrictions on user input.

The CharacterCasing Property

The CharacterCasing property of the TextBox control changes the case of characters in the text box as required by the application. For example, you might want to convert all characters entered in a text box used for entering a password to lowercase so that there are no problems due to case-sensitivity.

The values of the CharacterCasing property can be set to three values: CharacterCasing.Lower, CharacterCasing.Normal (the default value), and CharacterCasing.Upper.

The MaxLength Property

The MaxLength property of a TextBox or ComboBox control specifies the maximum number of characters that the user can enter into the control. This property is handy when you want to restrict the size of some fields, such as fields for telephone numbers or zip codes. This property is useful in scenarios in which you are adding or updating records in a database with the values entered in the controls; in such a case you can use the MaxLength property to prevent the user from entering more characters than the corresponding database field can handle.

TIP

The Scope of the MaxLength Property The MaxLength property affects only the text that is entered into the control interactively by the user. Programmatically, you can set the value of the Text property to a value that is longer than the value specified by the MaxLength property.

When the MaxLength property is zero (the default), the number of characters that can be entered is limited only by the available memory.

Guided Practice Exercise 3.2

As a Windows developer for a data analysis company, you recently developed a keyword searching form for your Windows application (refer to Guided Practice Exercise 3.1). The form asks for a filename and a keyword from the user, and then it searches for the keyword in the file and displays the number of lines that contain the keyword in the results group box. The form assumes that the entered keyword is a single word. In the solution in Guided Practice Exercise 3.1, if the keyword is not a single word, the form creates and throws a custom exception for that case. Since you created that solution, you have studied field-level validation techniques and realized that for this scenario, the use of field-level validation provides a much more elegant solution.

You now want to modify the keyword searching form. Its basic functionality is still the same as in Guided Practice Exercise 3.1, but you need to incorporate a few changes in the user interface. Initially the keyword text box and the Search button are disabled; you should enable these controls as the user progresses through the application. If the keyword entered by the user is not a single word, instead of throwing an exception, you need to display the error icon with the keyword text box and set an error message. The keyword text box should not lose focus unless it has valid data.

How would you create such a form?

You should try working through this problem on your own first. If you get stuck, or if you'd like to see one possible solution, follow these steps:

  1. Add a new form to your Visual C# .NET project. Name the form GuidedPracticeExercise3_2.cs.

  2. Place and arrange the controls on the form as shown in Figure 3.8. Name the TextBox control for accepting the filename txtFileName and the Browse button btnBrowse. Set the ReadOnly property of txtFileName to true. Name the TextBox control for accepting the keyword txtKeyword and the Button control btnSearch. Set the tab order of the form in the correct order so that the user's cursor is not placed in a read-only text box when the application starts.

  3. Add an OpenFileDialog control to the form and change its name to dlgOpenFile. Add an ErrorProvider component (errorProvider1) to the form. The ErrorProvider component is placed in the component tray.

  4. Double-click the form to attach the Load event handler to the form. Add the following code to handle the Load event of the Form control:

    private void GuidedPracticeExercise3_2_Load(
      object sender, System.EventArgs e)
    {
      // Disable the keyword text box and Search button
      txtKeyword.Enabled = false;
      btnSearch.Enabled = false;
      errorProvider1.SetIconAlignment(
        txtKeyword, ErrorIconAlignment.MiddleLeft);
    }
  5. Attach TextChanged and Validating event handlers to the txtKeyword control and add the following code:

    private void txtKeyword_TextChanged(
      object sender, System.EventArgs e)
    {
      if(this.txtKeyword.Text.Length==0)
        this.btnSearch.Enabled = false;
      else
        this.btnSearch.Enabled = true;
    }
    
    private void txtKeyword_Validating(object sender,
      System.ComponentModel.CancelEventArgs e)
    {
      if(this.txtKeyword.Text.Trim().IndexOf(' ') >= 0)
      {
        errorProvider1.SetError(txtKeyword,
         "You must only specify a single word");
        txtKeyword.Focus();
        txtKeyword.Select(0, txtKeyword.Text.Length);
      }
      else
        errorProvider1.SetError(txtKeyword, "");
    }
  6. Create a method named GetKeywordFrequency() that accepts a string and returns the number of lines containing it. Add the following code to the method:

    private int GetKeywordFrequency(string path)
    {
      int count = 0;
      if (File.Exists(path))
      {
        StreamReader sr =
          new StreamReader(txtFileName.Text);
        while (sr.Peek() > -1)
          if (sr.ReadLine().IndexOf(txtKeyword.Text)
            >= 0)
            count++;
      }
      return count;
    }
  7. Add the following code to the Click event handler of btnBrowse:

    private void btnBrowse_Click(
      object sender, System.EventArgs e)
    {
      if (dlgOpenFile.ShowDialog() == DialogResult.OK)
      {
        txtFileName.Text = dlgOpenFile.FileName;
        this.txtKeyword.Enabled = true;
        this.txtKeyword.Focus();
      }
    }
  8. Add the following code to the Click event handler of btnSearch:

    private void btnSearch_Click(
      object sender, System.EventArgs e)
    {
      if (errorProvider1.GetError(txtKeyword) != "")
        return;
      try
      {
        lblResult.Text = String.Format(
         "The keyword: '{0}' was found in {1} lines",
         txtKeyword.Text,
         GetKeywordFrequency(txtFileName.Text));
      }
      catch(Exception ex)
      {
        string msg = String.Format(
          "Message:\n {0}\n\n StackTrace:\n{1}",
          ex.Message, ex.StackTrace);
        MessageBox.Show(msg, ex.GetType().ToString());
      }
    }
  9. Insert the Main() method to launch form GuidedPracticeExercise3_2.cs. Set the form as the startup object for the project.

  10. Run the project. The keyword text box and the search button are disabled. Click the Browse button and select an existing file; this enables the keyword text box. Enter the keyword to search in the file; this enables the Search button. Click the Search button. If the keyword entered is in the wrong format (for example, if it contains two words), the ErrorProvider component shows the error message and the icon.

If you have difficulty following this exercise, review the section "Validating User Input," earlier in this chapter, and then try this exercise again.

The .NET Framework provides fully integrated support for exception handling. In fact, it allows you to raise exceptions in one language and catch them in a program written in another language. The try block is used to enclose code that might cause exceptions. The catch block is used to handle the exceptions raised by the code in the try block, and the finally block ensures that certain code is executed, regardless of whether an exception occurs.

The FCL provides a large number of exception classes that represent most of the exceptions that a program may encounter. If you prefer to create your own custom exception class, you can do so by deriving your exception class from the ApplicationException class.

This chapter describes a variety of ways to validate user input. The Windows Forms library provides an ErrorProvider component that is used to signal errors. You can also associate custom icons and error messages with the ErrorProvider component.

Apply Your Knowledge

Key Terms

Exercises

3.1 - Handling Exceptions

Recall that Step by Step 2.5 in Chapter 2 demonstrates the use of common dialog boxes through the creation of a simple rich text editor. This editor allows you to open and save a rich text file. You can also edit the text and change its fonts and colors. The program works fine in all cases except when you try to open or save a file that is already open; in that case, the program throws a System.IO.IOException exception.

The objective of this exercise is to make a robust version of this program that generates a warning about the open file rather than abruptly terminating the program.

Estimated time: 15 minutes

  1. Open a new Windows application in Visual C# .NET. Name it 316C03Exercises.

  2. Add a Windows form to the project. Name the form Exercise3_1.

  3. Place five Button controls on the form. Name them btnOpen, btnSave, btnClose, btnColor, and btnFont, and change their Text properties to &Open..., &Save..., Clos&e..., &Color..., and &Font..., respectively. Place a RichTextBox control on the form and name it rtbText. Arrange all the controls as shown in Figure 3.12.

Figure 3.12 This robust version of a simple rich text editor handles the exceptions of System.IO.IOException type.

  1. Drag and drop the following components from the toolbox onto the form: OpenFileDialog, SaveFileDialog, ColorDialog, and FontDialog.

  2. Switch to the code view and add the following using directive to the top of the program:

    using System.IO;
  3. Double-click the Open button to attach an event handler to this Click event. Add the following code to the event handler:

    private void btnOpen_Click(
      object sender, System.EventArgs e)
    {
      //Allow user to select only *.rtf files
      openFileDialog1.Filter =
        "Rich Text Files (*.rtf)|*.rtf";
      if(openFileDialog1.ShowDialog()
        == DialogResult.OK)
      {
        try
        {
          //Load the file contents
          //into the RichTextBox control
          rtbText.LoadFile(
          openFileDialog1.FileName,
          RichTextBoxStreamType.RichText);
        }
        catch(System.IO.IOException ioe)
        {
          MessageBox.Show(ioe.Message,
            "Error opening file");
        }
      }
    }
  4. Add the following code to handle the Click event of the Save button:

    private void btnSave_Click(
      object sender, System.EventArgs e)
    {
      //Default choice for saving file
      //is *.rtf but user can select
      //All Files to save with
      //another extension
      saveFileDialog1.Filter =
       "Rich Text Files (*.rtf)|*.rtf|" +
       "All Files (*.*)|*.*";
      if(saveFileDialog1.ShowDialog()
        == DialogResult.OK)
      {
        try
        {
          //Save the RichTextBox control's
          //content to a file
          rtbText.SaveFile(
          saveFileDialog1.FileName,
          RichTextBoxStreamType.RichText);
        }
        catch(System.IO.IOException ioe)
        {
          MessageBox.Show(ioe.Message,
            "Error saving file");
        }
      }
    }
  5. Add the following code to handle the Click event of the Close button:

    private void btnClose_Click(
      object sender, System.EventArgs e)
    {
      //close the form
      this.Close();
    }
  6. Add the following code to handle the Click event of the Color button:

    private void btnColor_Click(
      object sender, System.EventArgs e)
    {
      if(colorDialog1.ShowDialog()
        == DialogResult.OK)
      {
        //Change the color of the selected
        //text. If no text is selected,
        // change the active color
        rtbText.SelectionColor =
          colorDialog1.Color;
      }
    }
  7. Add the following code to handle the Click event of the Font button:

    private void btnFont_Click(
      object sender, System.EventArgs e)
    {
      if(fontDialog1.ShowDialog()
        == DialogResult.OK)
      {
        //Change the font of the selected
        //text. If no text is selected,
        //change the active font
        rtbText.SelectionFont =
          fontDialog1.Font;
      }
    }
  8. Insert the Main() method to launch the form. Set the form as the startup object.

  9. Run the project. Click on the Open button and try to open an already opened file. An error message appears, warning about the file already being open, as shown in Figure 3.13.

Figure 3.13 Instead of abnormal program termination, you now get an error message about the already open file.

3.2 -Validating User Input

One technique for input validation is to force the user to fix an erroneous field before allowing him or her to move to another field. To achieve this, you can set the Cancel property of the CancelEventArgs argument of the field's Validating event to false.

In this exercise, you create a login form (see Figure 3.14) that accepts a username and password. It forces the user to enter the username. The user should also be able to close the application by clicking the Cancel button, regardless of the validation status of the fields.

Figure 3.14 A nonsticky login form validates the input and allows users to close the application by clicking the Cancel button.

Estimated time: 15 minutes

  1. Open a Visual C# .NET Windows application in the Visual Studio .NET IDE. Name it 316C03Exercises.

  2. Add a new form to the application. Name it Exercise3_2.

  3. Place three Label controls (keep their default names), two TextBox controls (txtUserName and txtPassword), two Button controls (btnLogin and btnCancel), and an ErrorProvider component (errorProvider1) on the form. The ErrorProvider component is placed in the component tray. Arrange the controls in the form as shown in Figure 3.14.

  4. Change the ControlBox property of the form to false, the CharacterCasing property of the txtPassword control to Lower, and the CausesValidation property of the btnCancel control to false.

  5. Double-click the Form control to attach a Load event handler; add the following code to the event handler:

    private void Exercise3_2_Load(
      object sender, System.EventArgs e)
    {
      errorProvider1.SetIconAlignment(
         txtUserName,
         ErrorIconAlignment.MiddleLeft);
      errorProvider1.SetIconAlignment(
         txtPassword,
         ErrorIconAlignment.MiddleLeft);
    }
  6. Declare the following variable outside a method block in the class:

    //closingFlag is used to check if the
    //user has clicked the Close button
    private bool closingFlag = false;
  7. Add the following code to the Click event handler of the Cancel button:

    private void btnCancel_Click(
      object sender, System.EventArgs e)
    {
      closingFlag = true;
      this.Close();
    }
  8. Add the following code to the Click event handler of the Login button:

    private void btnLogin_Click(
      object sender, System.EventArgs e)
    {
      string strMessage = String.Format(
       "The following information:" +
       "\n\nUserName: {0}\n\nPassword: {1}" +
       "\n\n can now be passed to the " +
       "middle-tier for validation",
       txtUserName.Text, txtPassword.Text);
      MessageBox.Show(strMessage,
        "User Input Validation Succeeded");
    }
  9. Attach the following event handling code to the Validating events of both the txtUserName and txtPassword controls:

    private void
      txtUserNamePassword_Validating(
      object sender,
      System.ComponentModel.CancelEventArgs e)
    {
      TextBox fieldToValidate =
        (TextBox) sender;
    
      if (!closingFlag)
      {
      if(fieldToValidate.Text.Trim().Length
        == 0)
        {
          errorProvider1.SetError(
           fieldToValidate,
       "Please enter a value for this field");
          e.Cancel = true;
        }
        else if (
      fieldToValidate.Text.Trim().IndexOf(' ')
        >=0)
        {
          errorProvider1.SetError(
           fieldToValidate,
     "You may NOT have spaces in this field");
          fieldToValidate.Select(0,
           fieldToValidate.Text.Length);
          e.Cancel = true;
        }
      }
    }
  10. Attach the following event handling code to the Validated event of both the txtUserName and txtPassword controls:

    private void txtUserNamePassword_Validated(
      object sender, System.EventArgs e)
    {
      TextBox fieldToValidate =
        (TextBox) sender;
      errorProvider1.SetError(
        fieldToValidate, "");
    }
  11. Insert the Main() method to launch the form. Set the form as the startup object.

  12. Run the project. Click the Login button, and you are forced to enter the username. However, you can click the Cancel button to close the application.

Review Questions

  1. What is the default behavior of the .NET Framework when an exception is raised?

  2. What is the base class of all exceptions that provides basic functionality for exception handling? What are the two main types of exception classes and their purposes?

  3. Explain the Message and InnerException properties of the Exception class.

  4. What is the purpose of a try-catch block?

  5. How many catch blocks can be associated with a try block? How should they be arranged?

  6. What is the importance of a finally block?

  7. Can you associate custom error messages with the exception types defined by the CLR? If yes, how do you do it?

  8. What are some of the points you should consider before creating Custom exceptions?

  9. What is the importance of the Validating event?

  10. What is the purpose of the ErrorProvider component?

Exam Questions

  1. You are creating a data import utility for a personal information system that you recently designed. When the record in the source data file is not in the required format, the application needs to throw a custom exception. You want to keep the name of this exception class as InvalidRecordStructureException. Which of the following classes would you choose as the base class for the custom exception class?

    1. ApplicationException

    2. Exception

    3. SystemException

    4. InvalidFilterCriteriaException

  2. You are assisting a colleague in solving the compiler error that her code is throwing. This is the problematic portion of her code:

    try
    {
      bool success =
        GenerateNewtonSeries(500, 0);
      //more code here
    }
    catch(DivideByZeroException dbze)
    {
      //exception handling code
    }
    catch(NotFiniteNumberException nfne)
    {
      //exception handling code
    }
    catch(ArithmeticException ae)
    {
      //exception handling code
    }
    catch(OverflowException e)
    {
      //exception handling code
    }

    To remove the compilation error, which of the following ways would you rearrange the code?

    1.  try
      {
        bool success =
          GenerateNewtonSeries(500, 0);
        //more code here
      }
      catch(DivideByZeroException dbze)
      {
        //exception handling code
      }
      catch(ArithmeticException ae)
      {
        //exception handling code
      }
      catch(OverflowException e)
      {
        //exception handling code
      }
    2. try
      {
        bool success =
          GenerateNewtonSeries(500, 0);
        //more code here
      }
      catch(DivideByZeroException dbze)
      {
        //exception handling code
      }
      catch(Exception ae)
      {
        //exception handling code
      }
      catch(OverflowException e)
      {
        //exception handling code
      }
    3. try
      {
        bool success =
          GenerateNewtonSeries(500, 0);
        //more code here
      }
      catch(DivideByZeroException dbze)
      {
        //exception handling code
      }
      catch(NotFiniteNumberException nfne)
      {
        //exception handling code
      }
      catch(OverflowException e)
      {
        //exception handling code
      }
      catch(ArithmeticException ae)
      {
        //exception handling code
      }
    4. try
      {
        bool success =
          GenerateNewtonSeries(500, 0);
        //more code here
      }
      catch(DivideByZeroException dbze)
      {
        //exception handling code
      }
      catch(NotFiniteNumberException nfne)
      {
        //exception handling code
      }
      catch(Exception ae)
      {
        //exception handling code
      }
      catch(ArithmeticException e)
      {
        //exception handling code
      }
  3. You are required to debug a program that contains exception handling code. To understand the program better, you create a stripped-down version of it and include some MessageBox statements that give you clues about the flow of the program's execution. The program has the following code:

    try
    {
      int num = 100;
      int den = 0;
      MessageBox.Show("Message1");
      try
      {
        int res = num/den;
        MessageBox.Show("Message2");
      }
      catch(ArithmeticException ae)
      {
        MessageBox.Show("Message3");
      }
    }
    catch(DivideByZeroException dbze)
    {
      MessageBox.Show("Message4");
    }
    finally
    {
      MessageBox.Show("Message5");
    }

    Which of the following options describes the correct order of displayed messages?

    1. Message1
      Message2
      Message3
      Message4
      Message5
    2. Message1
      Message3
      Message5
    3. Message1
      Message4
      Message5
    4. Message1
      Message2
      Message4
      Message5
  4. In your Windows application, you want to determine the type of an exception. You have written the following code:

    try
    {
      try
      {
        throw new
          ArgumentOutOfRangeException();
      }
      catch(ArgumentException ae)
      {
        throw new ArgumentException(
          "Out of Range", ae);
      }
    }
    catch(Exception e)
    {
      MessageBox.Show(
      e.InnerException.GetType().ToString());
    }

    When the program containing this code segment is executed, what output is displayed by the message box?

    1. System.Exception

    2. System.ApplicationException

    3. System.ArgumentException

    4. System.ArgumentOutOfRangeException

  5. The Validating event of a TextBox control in your Windows application has the following code:

    01 private void textBox1_Validating(
      object sender, CancelEventArgs e)
    02 {
    03  try
    04  {
    05    MyValidatingCode();
    06  }
    07  catch(Exception ex)
    08  {
    09   
    10    textBox1.Select(0,
           textBox1.Text.Length);
    11    this.errorProvider1.SetError(
           textBox1, ex.Message);
    12  }
    13 }

    The MyValidatingCode() method validates the contents of the text box. If the contents are invalid, the MyValidatingCode() method throws an exception and retains control in the text box. (The line numbers in the code sample are for reference purposes only.) Which of the following lines of code should be in line 9?

    1. e.Cancel = true;

    2. e.Cancel = false;

    3. textBox1.CausesValidation = true;

    4. textBox1.CausesValidation = false;

  6. You have designed a windows form that works as a login screen. The form has two TextBox controls, named txtUserName and txtPassword. You want to ensure that user can enter only lowercase characters in the control. Which of the following options would you recommend?

    1. Set the form's KeyPreview property to true and program the KeyPress event of the form to convert uppercase letters to lowercase letters.

    2. Create a single event handler that is attached to the KeyPress event of both txtUserName and txtPassword. Program this event handler to convert the uppercase letters to lowercase.

    3. Use the CharacterCasing property.

    4. Use the Char.ToLower() method in the TextChanged event handler.

  7. You need to create a custom exception class in a Windows application. You have written the following code for the Exception class:

    public class KeywordNotFound:
      ApplicationException
    {
      public KeywordNotFoundException()
       {
       }
      public KeywordNotFoundException(
       string message, Exception inner)
       : base(message, inner)
       {
       }
    }

    A peer reviewer of the code finds that you did not follow some of the best practices for creating a custom exception class. Which of the following suggestions do you need to incorporate? (Choose all that apply.)

    1. Name the exception class KeywordNotFoundException.

    2. Derive the exception class from the base class Exception instead of ApplicationException.

    3. Add one more constructor to the class, with the following signature:
      public KeywordNotFoundException(
        string message) : base(message)
      {
      }
    4. Add one more constructor to the class, with the following signature:
      public KeywordNotFoundException(
        Exception inner) : base(inner)
      {
      }
    5. Derive the exception class from the base class SystemException instead of ApplicationException.

  8. In your Windows application, you have created a dialog box that allows users to set options for the application. You have also created a Help button that users can press to get help on various options for the dialog box. You validate the data entered by the user in a text box titled Complex Script. If the user enters an invalid value, you set the focus back in the control by setting the Cancel property of the CancelEventArgs object to true. While you are testing the application, you discover that when you enter invalid data in the text box, you cannot click the Help button unless you correct the data first. What should you do to correct the problem?

    1. Set the CausesValidation property of the TextBox control to false.

    2. Set the CausesValidation property of the TextBox control to true.

    3. Set the CausesValidation property of the Help button to false.

    4. Set the CausesValidation property of the Help button to true.

  9. You are writing exception handling code for an order entry form. When an exception occurs, you want to get information about the sequence of method calls and the line number in the method where the exception occurs. Which property of the Exception class could help you?

    1. HelpLink

    2. InnerException

    3. Message

    4. StackTrace

  10. Which of the following statements is true regarding the following usage of the throw statement?

    catch(Exception e)
    {
      throw;
    }
    1. The throw statement catches and rethrows the current exception.

    2. The throw statement catches, encapsulates, and then rethrows the current exception.

    3. The throw statement must be followed by an exception object to throw.

    4. The throw statement transfers control to the finally block that follows the catch block.

  11. You are creating a Windows form that works as a login screen for an order entry system designed for the sales department of your company. Which of the following strategies would you follow?

    1. Design a ValidateUser() method. Throw a new custom exception EmployeeNotFound when the entered username is not in the database.

    2. Design a ValidateUser() method. Throw an ArgumentException exception when the user types special characters in the username field or the password field.

    3. Design a ValidateUser() method that returns true if the username and password are correct and otherwise returns false.

    4. Design a ValidateUser() method. Throw an ApplicationException exception when the entered username is not in the database.

  12. You want to capture all the exceptions that escape from the exception handling code in your application and log them in the Windows event log. Which of the following techniques would you use?

    1. Write all the code of the Main() method inside a try block, attach a generic catch block to that try block, and handle the exception there.

    2. Write all the code of the Main() method inside a try block, attach a catch block that catches all exceptions of type Exception, and write code to make an entry in the event log.

    3. Program the ProcessExit event handler of the AppDomain class.

    4. Program the UnhandledException event handler of the AppDomain class.

  13. Which of the following is the most robust way to record the unhandled exceptions in an application?

    1. Create an entry in the Windows event log.

    2. Create an entry in the application's custom log file.

    3. Create an entry in a table in a Microsoft SQL Server 2000 database.

    4. Send an email message using SMTP.

  14. The structured exception handling mechanism of the .NET Framework allows you to handle which of the following types of exceptions? (Choose all the answers that apply.)

    1. Exceptions from all CLS-compliant languages

    2. Exceptions from non-CLS-compliant languages

    3. Exceptions from unmanaged COM code

    4. Exceptions from unmanaged non-COM code

  15. Which of the following statements is true about the following code segment?

    const int someVal1 = Int32.MaxValue;
    const int someVal2 = Int32.MaxValue;
    int result;
    checked
    {
      result = someVal1 * someVal2;
    }
    1. The code generates an OverflowException exception.

    2. The code executes successfully without any exceptions.

    3. The code causes a compile-time error.

    4. The code executes successfully, but the value of the resulting variable is truncated.

Answers to Review Questions

  1. The .NET Framework terminates the application after displaying an error message when an exception is raised.

  2. The Exception class is the base class that provides common functionality for exception handling. The two main types of exceptions derived from the Exception class are SystemException and ApplicationException. SystemException represents the exceptions thrown by the CLR, and ApplicationException represents the exceptions thrown by the applications.

  3. The Message property describes the current exception. The InnerException property represents an exception object associated with the current exception object. This property is helpful when a series of exceptions are involved because each new exception can preserve the information about the previous exception by storing it in the InnerException property.

  4. The try block is used to enclose code that may raise an exception. The catch block handles the exception raised by the code in the try block.

  5. Zero or more catch blocks can be associated with a try block. If there is no catch block associated with a try block, a finally block should follow the try block; otherwise, a compile-time error occurs. The catch blocks should be arranged from top to bottom in the order of specific to general exception types; otherwise, a compile-time error occurs.

  6. The code contained by the finally block always executes, regardless of whether any exception occurs in the try block. Therefore, you can use the finally block to write cleanup code—such as closing data connections, closing files, and so on—that needs to happen, regardless of whether an exception occurs.

  7. Yes, you can associate custom error messages with the exception classes defined by the CLR in order to provide more meaningful information to the calling code. The constructor of these classes that accepts as its parameter the exception message can be used to pass the custom error message.

  8. Custom exceptions should be derived from ApplicationException and should be created only if any of the existing classes do not meet the requirements of your application. The custom exception classes should have names that end with the word Exception and should implement three constructors (a default constructor that takes no arguments, a constructor that takes a string argument, and a constructor that takes a string as well as an Exception object) of the base class.

  9. The Validating event is the ideal place for storing the field-level validation logic for a control. The Validating event handler can be used to cancel the event if validation fails, thus forcing the focus to the control. This requires the user to enter correct data.

  10. The ErrorProvider component in the Visual Studio .NET toolbox can be used to show validation-related error icons and error messages to the user.

Answers to Exam Questions

  1. A. When you create a class for handling custom exceptions in your programs, the best practice is to derive it from the ApplicationException class. The SystemException class is for the system-defined exceptions. The Exception class is the base class for both the ApplicationException and SystemException classes and should not be subclassed. For more information, see the section "Creating and Using Custom Exceptions" in this chapter.

  2. C. When you have multiple catch blocks associated with a try block, you must write them in the order of most specific to least specific. The catch block corresponding to the ArithmeticException exception should come at the end because it is more general than DivideByZeroException, NotFiniteNumberException, and OverFlowException, which are derived from it. For more information, see the section "The catch Block" in this chapter.

  3. B. When an exception occurs in a try block, the program searches for a matching catch block associated with that try block. Because the ArithmeticException type is more general than the DivideByZeroException type, all DivideByZeroException exceptions are handled in the catch block that catches the ArithmeticException exception. In all cases, the finally block is executed. For more information, see the sections "The catch Block" and "The finally Block" in this chapter.

  4. D. The message box displays a System.ArgumentOutOfRangeException exception because that is the exception that you caught and wrapped in the InnerException property of the exception that was caught later by the outer catch block. For more information, see the section "The throw Statement" in this chapter.

  5. A. When you want to retain the control inside a control after the Validating event is processed, you must set the Cancel property of the CancelEventArgs argument in the Validating event. The correct answer is therefore e.Cancel = true. The CausesValidation property has a different purpose: It is used to decide whether a Validating event is fired for a control. For more information, see the section "The Validating Event" in this chapter.

  6. C. The CharacterCasing property, when set to CharacterCasing.Lower for a TextBox control, converts all uppercase letters to lowercase as you type them. It is the preferred way to enforce either lowercase or uppercase input in a text box. For more information, see the section "Other Properties for Validation" in this chapter.

  7. A and C. As a good exception handling practice, you should end the name of the exception class with the word Exception. In addition, an exception class must implement three standard constructors. The missing constructor is the one given in Answer C. For more information, see the section "Creating and Using Custom Exceptions" in this chapter.

  8. C. When you want a control to respond, regardless of the validation status of other controls, you should set the CausesValidation property of that control to false. Therefore, the Help button should have its CausesValidation property set to false. For more information, see the section "The CausesValidation Property" in this chapter.

  9. D. The StackTrace property of the Exception class and the classes that derive from it contain information about the sequence of method calls and the line numbers in which exceptions occur. Therefore, it is the right property to use. For more information, see the section "Understanding Exceptions" in this chapter.

  10. A. The throw statement re-throws the current exception. For more information, see the section "The throw Statement" in this chapter.

  11. C. It is obvious that the user might make typing mistakes while typing his or her username or password. You should not throw exceptions for these situations; you should instead design a ValidateUser() method that returns a result indicating whether the login is successful. For more information, see the section "Validating User Input" in this chapter.

  12. D. To capture all unhandled exceptions for an application, you must program the UnhandledEvent event handler of the AppDomain class. For more information, see the section "Managing Unhandled Exceptions" in this chapter.

  13. A. Logging on to the Windows event log is the most robust solution because the other solutions have more assumptions that may fail. For example, your application might loose connectivity with the database or with the SMTP server, or you might have problems writing an entry to a custom log file. For more information, see the section "Managing Unhandled Exceptions" in this chapter.

  14. A, B, C, and D. The .NET Framework allows you to handle all kinds of exceptions, including cross-language exceptions, for both CLS- and non-CLS-compliant languages. It also allows you to handle exceptions from unmanaged code, both COM as well as non-COM. For more information, see the section "Understanding Exceptions" in this chapter.

  15. C. When constant values appear inside the checked statement, they are checked for overflow at compile time. Because you are multiplying the two maximum possible values for integer, the result cannot be stored inside an integer. The compiler detects this problem and generates a compile-time error. For more information, see the section "Handling Exceptions" in this chapter.

Suggested Readings and Resources

  1. Visual Studio .NET Combined Help Collection:

    • "Exception Management in .NET"

    • "Exception Handling Statements"

    • "Best Practices for Exception Handling"

  2. Harvey M. Dietel, et al. C# How to Program. Prentice Hall, 2001.

  3. Jeffrey Richter. Applied Microsoft .NET Framework Programming. Microsoft Press, 2001.

  4. Exception Management in .NET. msdn.microsoft.com/library/en-us/dnbda/html/exceptdotnet.asp.

800 East 96th Street, Indianapolis, Indiana 46240

sale-70-410-exam    | Exam-200-125-pdf    | we-sale-70-410-exam    | hot-sale-70-410-exam    | Latest-exam-700-603-Dumps    | Dumps-98-363-exams-date    | Certs-200-125-date    | Dumps-300-075-exams-date    | hot-sale-book-C8010-726-book    | Hot-Sale-200-310-Exam    | Exam-Description-200-310-dumps?    | hot-sale-book-200-125-book    | Latest-Updated-300-209-Exam    | Dumps-210-260-exams-date    | Download-200-125-Exam-PDF    | Exam-Description-300-101-dumps    | Certs-300-101-date    | Hot-Sale-300-075-Exam    | Latest-exam-200-125-Dumps    | Exam-Description-200-125-dumps    | Latest-Updated-300-075-Exam    | hot-sale-book-210-260-book    | Dumps-200-901-exams-date    | Certs-200-901-date    | Latest-exam-1Z0-062-Dumps    | Hot-Sale-1Z0-062-Exam    | Certs-CSSLP-date    | 100%-Pass-70-383-Exams    | Latest-JN0-360-real-exam-questions    | 100%-Pass-4A0-100-Real-Exam-Questions    | Dumps-300-135-exams-date    | Passed-200-105-Tech-Exams    | Latest-Updated-200-310-Exam    | Download-300-070-Exam-PDF    | Hot-Sale-JN0-360-Exam    | 100%-Pass-JN0-360-Exams    | 100%-Pass-JN0-360-Real-Exam-Questions    | Dumps-JN0-360-exams-date    | Exam-Description-1Z0-876-dumps    | Latest-exam-1Z0-876-Dumps    | Dumps-HPE0-Y53-exams-date    | 2017-Latest-HPE0-Y53-Exam    | 100%-Pass-HPE0-Y53-Real-Exam-Questions    | Pass-4A0-100-Exam    | Latest-4A0-100-Questions    | Dumps-98-365-exams-date    | 2017-Latest-98-365-Exam    | 100%-Pass-VCS-254-Exams    | 2017-Latest-VCS-273-Exam    | Dumps-200-355-exams-date    | 2017-Latest-300-320-Exam    | Pass-300-101-Exam    | 100%-Pass-300-115-Exams    |
http://www.portvapes.co.uk/    | http://www.portvapes.co.uk/    |