![]() |
General Programming »
Exception Handling »
General
Intermediate
User Friendly Exception HandlingBy wumpus1A flexible framework for user-friendly .NET exception handling, and automatically notifying developers of problems before users do |
VB.NET 1.1, Win2K, WinXP, Win2003VS.NET2003, Architect, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

In Visal Basic 6, there was no way to globally handle all of the errors in
your application. You could simulate one by wrapping every sub or
function in an On Error statement, but that was rather painful.
One of the great features of .NET is the very powerful thread and application level exception handling mechanisms that make developing a global error handler practical. One of the first things I set out to do when starting with .NET was finally implement all those great global error handling ideas I had considered, but had to abandon in the last 5 years of working with VB5 and VB6. I found an excellent article on Code Project documenting the basic techniques of unhandled exception handling in .NET, so armed with that, I set out to implement my "dream" error handling system.
At the time of the unhandled exception, my
UnhandledExceptionHandler class will:
Try..Catch blocks for every possible
error condition, no matter how rare, you can simply rely on the solid, built-in
global handler to deal with those oddball scenarios-- and to notify you when
they happen!
So then the natural question that most developers ask is, "When should I catch exceptions"? And it's a very good question. Here are some guidelines that I have found useful.
System.Exception whenever possible; try to catch just the
specific errors that are specific to that block of code. Catch
System.IO.FileNotFound instead. Application.ThreadExceptionHandler, which works for the main
application thread of a Windows Forms application: AddHandler Application.ThreadException, AddressOf ThreadExceptionHandler
And the AppDomain.CurrentDomain.UnhandledException handler,
which works for Console applications:
AddHandler System.AppDomain.CurrentDomain.UnhandledException, _
AddressOf UnhandledExceptionHandler
This leaves out one large class of .NET applications: web apps. I've experimented with this, and I do not believe it is desirable to handle web exceptions using the exact same classes that you use to handle Console and WinForms exceptions. They have different needs. I do have a variant of this class I use in server-side Web Services and ASP.NET applications, but it's significantly different. This article will only cover exception handling techniques appropriate for Console or Windows Forms executables.
The other thing we need to be aware of is multi-threaded applications. The default exception handling only covers the main application thread. If you are spawning additional threads in your application, you must remember to set up the unhandled exception handler for each new thread you spawn.
Setting up the unhandled exception handler is very easy, but it must be done
as soon as possible at application startup by calling
UnhandledExceptionManager.AddHandler()
For Windows Forms applications:
Public Sub Main()
UnhandledExceptionManager.AddHandler()
Dim frm As New Form1
Application.Run(frm)
End Sub
For Console applications:
Sub Main()
UnhandledExceptionManager.AddHandler(True)
Console.WriteLine("Hello World!")
DoSomeWork()
End Sub
This is a "set it and forget it" operation. Once you've hooked up the
handler, everything else is automatic from this point on. The only
difference between the two initialization routines is the single optional
Boolean parameter blnConsoleApp. Unfortunately I couldn't determine
any way to distinguish between a Console and a Windows Forms app at runtime; the
distinction is important for reasons I'll cover later.
The UnhandledExceptionManager class can be explicitly configured
at initialization time through its parameters, or it can be configured passively
through ambient App.Config settings. The App.Config settings are all optional,
but look like this (I chose all defaults):
<add key="UnhandledExceptionManager/EmailTo"
value="jatwood@spamnotcodinghorror.com" />
<add key="UnhandledExceptionManager/ContactInfo"
value="Jeff Atwood at 123-555-1212" />
<add key="UnhandledExceptionManager/IgnoreDebug" value="True" />
<add key="UnhandledExceptionManager/SendEmail" value="True" />
<add key="UnhandledExceptionManager/LogToFile" value="True" />
<add key="UnhandledExceptionManager/LogToEventLog" value="False" />
<add key="UnhandledExceptionManager/TakeScreenshot" value="True" />
<add key="UnhandledExceptionManager/DisplayDialog" value="True" />
<add key="UnhandledExceptionManager/EmailScreenshot" value="True" />
<add key="UnhandledExceptionManager/KillAppOnException" value="True" />
The most important setting is DisplayDialog, which defaults to
True. For a Windows Forms app, when an Unhandled Exception occurs, we generate a
user-friendly error dialog based on the GUI design laid out by Alan Cooper in his book
About
Face: The Essentials of User Interface Design, in the chapter titled The
End of Errors.
To protect the end user from "geek speak", the Exception dialog hides most of the detailed diagnostic information until the "More >>" button is clicked. The animated screenshot at the top of this article demonstrates the use of this button.
Of course in a console app, we can't throw up a traditional WinForms dialog, so we simply dump the text to the console, like so:
But the really cool thing about the UnhandledExceptionHandler
class is that, in addition to putting a friendly UI 'face' on our exceptions, it
also does a lot of things for us behind the scenes. First of all, it takes a
screenshot of the exception and logs the exception to a text file. These files
are stored in the same path as the executable. Note that, for Smart Client apps
which have a remote URL for a path, these files are named with filesafe versions
of the URL and stored in the root folder of the hard drive.
By default, UnhandledExceptionHandler bundles up the text log
and the screenshot into an exception email. This information is emailed to the
address(es) of your choice via the built in, native SMTP class
SimpleMail, assuming you've configured it with a valid SMTP server
address. The email contains:
As you can see, the screenshot that was captured to the .PNG file is attached directly to the email. The screenshot is taken immediately after the Unhandled Exception, but before any UI appears; this way we capture the "scene of the crime", and hopefully have some idea what the user was doing to trigger the exception (damn users!). This provides additional valuable information that sometimes isn't reflected in the stack trace.
Also note that the email from: address, pictured above, defaults to Filename@Machine.Domain.com, a naming scheme I'm arrived at after a few years of standard email notifications; this way you know "who" mailed you (what application), and where it is coming from (what computer).
But wait-- there's more! I don't frequently enable this in my apps, but the code can also write the same detailed exception information to the Application Event Log, if you'd like to see them there.
Two final observations on Unhandled Exceptions before we close our discussion.
UnhandledExceptionManager.KillAppOnException property, which
defaults to True. Set it to taste, it works either way.
Return right smack dab in the middle of your function. Beware!
That pretty much covers Unhandled Exceptions, which is great, but.. what
about exceptions that we want to handle? In other words, situations that
we know can happen, and we want to specifically protect the user from-- without
terminating our application? For that, I included
HandledExceptionManager, which leverages the same UI. It's like a
MessageBox specifically designed for exceptions, that supports an
optional email notification.
The syntax is very similar to MessageBox, in fact:
Public Overloads Shared Function ShowDialog(ByVal strWhatHappened As String, _
ByVal strHowUserAffected As String, _
ByVal strWhatUserCanDo As String, _
ByVal objException As System.Exception, _
Optional ByVal Buttons As MessageBoxButtons = MessageBoxButtons.OK, _
Optional ByVal Icon As MessageBoxIcon = MessageBoxIcon.Warning, _
Optional ByVal DefaultButton _
As UserErrorDefaultButton = UserErrorDefaultButton.Default) _
As DialogResult
But it can also be called using an even simpler overloaded method.
Public Overloads Shared Function ShowDialog(ByVal strWhatHappened As String, _
ByVal strHowUserAffected As String, _
ByVal strWhatUserCanDo As String) As DialogResultThe sample
solution includes an interactive demonstration of all the different parameters
you can use to call the HandledExceptionManager.ShowDialog method.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 17 Aug 2004 Editor: Sean Ewington |
Copyright 2004 by wumpus1 Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |