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
- Open the
Puzzler.sln file, and build the solution.
Review the Application
- 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.
-
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
- 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
-
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.
- 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
-
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.
-
Run the Enterprise Library Configuration tool. From the
Windows Start menu select All
Programs | Microsoft patterns and practices | Enterprise
Library | Enterprise Library Configuration.
-
Select File | Open Application and browse
to the App.config file located
here.
- Right click on the Application and
select New | Exception Handling Application Block.
- 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.
- 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.
- 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.
- Add a Logging Handler for the
System.Exception exception type.
Note that this automatically includes
the Logging and Instrumentation Application Block in your
configuration.
- Set the LogCategory of
the Logging Handler to General.
- 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
-
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
-
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.
- Right click on the new PuzzlerUI project
and select Properties from the popup menu.
-
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.
-
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
- 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".
- 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.
- Close the application.
Add Global Exception Handling
- 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.
- 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, "");
}
-
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.
- 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).
- 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.
- 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.
- 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);
}
- 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.
- Close the application.
|