|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn 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 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
About Unhandled ExceptionsThe great thing about a good global exception handler is that it makes the rest of your code easier to read. Rather than cluttering up your code withTry..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.
BackgroundThis approach leverages two specific exception handlers. TheApplication.ThreadExceptionHandler, which works for the main
application thread of a Windows Forms application: AddHandler Application.ThreadException, AddressOf ThreadExceptionHandler
And the 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. Using UnhandledExceptionManagerSetting up the unhandled exception handler is very easy, but it must be done
as soon as possible at application startup by calling
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 The <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
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
By default,
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.
Using HandledExceptionManagerThat 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
The syntax is very similar to 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.
ConclusionI think that wraps it up. I've used these classes on a few different projects now with great results, and I hope they work well for you too. My last article was criticized for not being detailed enough, so I tried to go into greater depth for this one. That said, there are still a bunch of things in the source I either didn't have space to cover, or that didn't neatly fit within the scope of exception handling. There are many more details and comments in the source code provided at the top of the article, so check it out. And please don't hesitate to provide feedback, good or bad!History
| ||||||||||||||||||||