Exercise 1: Logging Exceptions

In this exercise you will take an application without exception handling, and add local and global exception handlers that log the exceptions to the Event Log using the Exception Management Application Block.

First step

  1. Open the Puzzler.sln file, and build the solution.

Review the Application

  1. Open the Puzzler.cs file in the designer. This application performs two functions, it checks the spelling of words against a dictionary (unix dict for size) and it uses the dictionary to generate a list of words that can be constructed from a character list.
  2. There is currently no exception handling in the application. Run the application, and attempt to add a word which contains numbers to the dictionary (type "ab123" in the word to check textbox and click Add Word). An unhandled exception will occur, which will break into the debugger. Click Continue to allow the application to exit and return to Visual Studio.  

Install Enterprise Library Instrumentation

  1. Install the enterprise instrumentation by selecting from the Start Menu: Start | All Programs | Microsoft patterns & practices | Enterprise Library | Install Services. Enterprise Library has built-in instrumentation that increments performance counters and sends out WMI events. If these performance counters and event types are not registered, then you will get warning events in your event log. If you do not wish to have the inbuilt instrumentation, remove the defines  USEWMI;USEEVENTLOG;USEPERFORMANCECOUNTER from the Common project, and recompile it. See this post for more information.

Add Try/Catch Exception Handling

  1. Select the PuzzlerUI project.  Select the Project | Add Reference … menu command.  Select the Browse button and select the following assemblies located here.
     
    • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll.
    While this assembly is the only assembly required for the ExceptionHandling API, other assemblies may need to be available in the bin\debug directory to provide specific exception handling functionality, which you will add later.
  2. Right click over Puzzler.cs in the solution explorer and select View Code. Find the btnAddWord_Click method, and add a try/catch section around the AddWord and SetError calls. (Inserted code in bold):
    private void btnAddWord_Click(object sender, System.EventArgs e)
    {
      try
      {
        PuzzlerService.Dictionary.AddWord(txtWordToCheck.Text);
        errorProvider1.SetError(txtWordToCheck, "");
      }
      catch (Exception ex)
      {
        if (Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.HandleException(ex, "UI Policy"))
        throw;
        MessageBox.Show("Failed to add word " + txtWordToCheck.Text + ", please contact support.");
      }
    }
    NOTE: It is very important to just use the throw statement, rather than throw ex. If you have "throw ex", then the stack trace of the exception will be replaced with a stack trace starting at the re-throw point, which is usually not the desired effect.

Configure the Application to Use Exception Management

  1. Add a new Application configuration file (App.config) to the PuzzlerUI project.  Click on the PuzzlerUI project.  Select the File | Add New Item menu command.  Select the Application configuration file template.  Leave the Name as App.config.

     

  2. Run the Enterprise Library Configuration tool.  From the Windows Start menu select All Programs | Microsoft patterns and practices | Enterprise Library | Enterprise Library Configuration.

     

  3. Select File | Open Application and browse to the App.config file located here.

     

  4. Right click on the Application and select New | Exception Handling Application Block.
  5. Add a new exception policy. Change the name of the new policy from Exception Policy to UI Policy.
    The policy name here must match that specified in the code. Typically you would use constants to help prevent typographical errors, especially since the exception handling typically is not tested as thoroughly as normal code paths.
  6. Add the exception type System.Exception to UI Policy.
    This is a filter that specifies which exceptions should be processed by the exception handling code, and what handlers to run. In this case, all exceptions derived from System.Exception will be processed.
  7. Set the PostHandlingAction for the System.Exception to None.
    This will cause all exceptions to be handled internally within the exception handling code, and the caller will not be requested to re-throw the exception with its catch block.
  8. Add a Logging Handler for the System.Exception exception type.
    Note that this automatically includes the Logging and Instrumentation Application Block in your configuration.
  9. Set the LogCategory of the Logging Handler to General.
  10. Save the current application in the Enterprise Library Configuration Console using File | Save All.
    This will save the changes to your App.Config file as well as adding configuration files to your project directory for each block you have utilized.

Change the application to include the exception logging assembly

  1. Select the PuzzlerUI project.  Select the Project | Add Reference … menu command.  Select the Browse button and select the following assemblies located here.
     
    • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll.

     

    Because one of the goals of the Enterprise Library is to keep the blocks de-coupled, it is possible to use the Exception Handling block without needing the Logging block (e.g. by creating your own exception handler). If you want to use the two blocks together, then you need to use this assembly, which contains an Exception Handler that logs via the logging block. 

Add a Post-Build step

  1. The configuration settings file is expected to be found in the same directory as the application configuration file.  Therefore you need to copy the exception handling block configuration file to the bin\debug directory before running the application.  You could copy the file manually, but it would be preferable to automate the copy as part of the build process.

     

  2. Right click on the new PuzzlerUI project and select Properties from the popup menu.
  3. In the project properties dialog select Build Events under Common Properties and set the Post-build event command line property to the value below.

     

    copy "$(ProjectDir)\*.config" "$(TargetDir)" > Nul
    Redirecting the output to the Nul device will prevent the message 1 file(s) copied from appearing in your build output. Note that this will only copy to the debug directory and would need to be changed if you wished to create a release version of the application.
  4. Select the Build | Build Solution menu command.  The last thing you will see in the build output is the message: 
     
    Performing Post-Build Event...
     
    If the post-build event fails for some reason, this will be shown as a build error.

     

    Every time you build the application the post build event will run, copying all the config files in the PuzzlerUI directory to the bin directory.

Run the Application

  1. Run the Application. Try adding a number (type a number in the Word to check text box, and click Add Word) - a MessageBox is displayed with an error - "Failed to add word …, please contact support".
  2. Check the Event Log by using the Event Viewer (Control Panel | Administrative Tools | Event Viewer). Look at the top of the Application Log. The exception will be logged.
    After the previous exception the Exception Management Block will have written an entry in the Application Event Log using the Logging Block.
  3. Close the application.

Add Global Exception Handling

  1. While it is possible to put try/catch sections around all the event handlers in an application, it is usually better to provide a single generic handler that fires whenever an unhandled exception occurs within the application.
  2. Open Puzzler.cs, and remove the exception handling from the btnAddWord_Click method:
    private void btnAddWord_Click(object sender, System.EventArgs e)
    {
      PuzzlerService.Dictionary.AddWord(txtWordToCheck.Text);
      errorProvider1.SetError(txtWordToCheck, "");
    }
  3. Open the Startup.cs file.
    Application execution begins here. There are two events you can use to listen for unhandled exceptions:
    The Application.ThreadException event is raised when an unhandled exception occurs on the thread that is executing the Application.Run method.
    If an exception is raised during that handler, or occurs on a different thread to the UI, then the AppDomain.UnhandledException event will fire.

     

  4. Add the following method to the Startup class, which will be used to handle exceptions.
    public static void HandleException(Exception ex, string policy)
    {
      Boolean rethrow = false;
      try
      {
        rethrow = Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.HandleException(ex, policy);
      }
      catch (Exception innerEx)
      {
        string errorMsg = "An unexpected exception occured while calling HandleException with policy '" + policy + "'. ";
        errorMsg += Environment.NewLine + innerEx.ToString();
        MessageBox.Show(errorMsg, "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        throw ex;
      }
      if (rethrow)
      {
        throw ex; // WARNING: This will truncate the stack of the exception
      }
      else
      {
        MessageBox.Show("An unhandled exception occurred and has been logged. Please contact support.");
      }
    }
    This method will use the exception handling block, and will also display valid information if there is a problem with the exception handling block itself (e.g. missing configuration).
    It will also display a message to the user if the exception is swallowed (i.e. not re-thrown).
  5. Add an event handler for the application ThreadException event.
    public static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {
      HandleException(e.Exception, "UI Policy");
    }
    This event handler will use the policy that you defined before, for the UI layer. In the next exercise you will customize this policy to allow certain types of exception to "escape" and shut the application down.
  6. Add an event handler for the app domain UnhandledException event.
    public static void AppDomain_UnHandledException(object sender, System.UnhandledExceptionEventArgs e)
    {
      if (e.ExceptionObject is System.Exception)
      {
        HandleException((System.Exception)e.ExceptionObject, "Unhandled Policy");
      }
    }
    This handler will use a new policy, named Unhandled Policy, that you will set up in the next exercise. The Unhandled Policy should almost always just log the exception, and not re-throw.
  7. Connect the event handlers to the events at the begining of the application by including the following code in the Main method (Inserted code in bold).
    static void Main()
    {
      Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
      AppDomain.CurrentDomain.UnhandledException += new System.UnhandledExceptionEventHandler(AppDomain_UnHandledException);
      Puzzler f = new Puzzler();
      Application.Run(f);
    }
  8. Run the Application. Try adding a number (type a number in the Word to check text box and click Add Word) - a MessageBox is displayed - "An unhandled exception occurred and has been logged. Please contact support.". Look in the event log for the logged exception.
  9. Close the application.