DevCity.NET - http://devcity.net
Structured Exception Handling in .NET (ApplicationException)
http://devcity.net/Articles/284/1/article.aspx
Arnaldo Sandoval
I began my run in the IT field back in 1975 while at the University getting my Industrial Engineer Bachelor Degree; using Wang Basic language and Burroughs 6700's FORTRAN and COBOL, working as a Teacher’s assistance for a fist full of dollars, since then I had worked in four different countries: Venezuela, USA, Mexico and Australia, the Business Basic Language the main skill expected from me; learning Unix, C, PASCAL, Uniplex, WordPerfect, 20/20 in the lates 80s, Sybase in the early 90s, Basis BBx in the 90s, Microsoft’s VBA in the mid 90s, Visual Basic version 3.0 around 1996; moved to Australia in the lates 90s, here I kept learning, Transoft’s Universal Business Language, Oracle, Microsoft SQL Server, Visual Basic .Net; working for a major Australian company in the building material market. I had worked for Computers Manufacturing companies (MAI Basic Four after reading the Wikepedia definition, it feels good being an active part of the industry), and high rollers companies (DOLE, Pepsico when they operated Pizza Hut, KFC, Taco Bell and PFS, Boral Limited), roaming the world while doing so, exposed to cutting edge technologies of their time, creating it when the opportunity required so. I currently look after an Oracle data warehouse, sourcing its data from four or five legacy applications, servicing Crystal Reports and Cognos Cubes, developed VB and Net solutions; I could claim the phrase “I had been there, done that” suits me like a globe, always addressing any challenge with an engineer mind, which is different to an IT mindset.  
by Arnaldo Sandoval
Published on 11/4/2006
 

Everyone tries to write error free applications, but we all know end users are smarter than us, finding ways to break our well protected applications.   Then we are given the answer "I don't know" when we ask the obvious question "What were you doing?", or "I can't find it" to our inquiry "Did you write down the error message?"

This article guides you through  the .Net Framework features at your disposal to handle applications' errors, also known as Exceptions.  Once you understand the concepts, the code supplied could be integrated into your existing or new applications, providing  comprehensive Exceptions support that will benefit your endusers and support team.


Introduction

INTRODUCTION

Everyone tries to write error free applications, but we all know end users are smarter than us, finding ways to break our well protected applications.   Then we are given the answer "I don't know" when we ask the obvious question "What were you doing?", or "I can't find it" to our inquiry "Did you write down the error message?" 

Messages like the one above may frighten end users.  They may think it was their fault, preferring to hide any evidence from us, perhaps thinking "This guy is making a lot of money and wants me to look after his/her shortcomings".

NET FRAMEWORK EXCEPTION HANDLING

The .Net Framework provides several Exception Handling classes, We implement them here and there, We try our best to trap as many errors as we can, returning nice error messages to the user, like the one below:

All the .Net Framework classes derive from the SystemException one.  It also provides the ApplicationException class as well. It is Microsoft's recommendation that we implement the ApplicationException class in our applications.  This recommendation has triggered several discussions on the Internet with opinions in favour or against its usage.   Joining this discussion is pointless, however  there are links to some of them in the References section in this thread.

The code posted in this thread implements an Exception Handling class based on the suggested ApplicationException class, with some value added properties and method you may find helpful. It is coded in VB.Net 2003 as there are plenty of versions written in C#. It also includes a test project that checks the additional features implemented.


STRUCTURED EXCEPTION HANDLING (SEH)

In Barton Friedland's article .NET Anatomy - Structured Exception Handling in .NET he wrote "structured exception handling (SEH), a service built into the core of .NET and available to all languages supported by it. Since the infrastructure for this service is built-in, there is very little code required to take full advantage of these features." which is true and fascinating.

The .Net Framework's built-in Exception Services give us so much information about the environment of the process triggering it, plus additional information exposed by the .Net Framework itself, that we should be able to develop a robust Exception Handling "component" that we can use in all our applications.

Before getting to the detail of the Exception Handling Component, let's overview what the .Net Framework has to offer on this matter.

TRY-CATCH-FINALLY-END TRY

We all know about the Try-Catch-Finally-End Try error trapping block; it goes like this:

 Public Sub MyMethod()
   '
   ' Code before the Try-End Try block
   '
   Try
      '
      ' You application code goes here
      '
   Catch se As SystemException
      '
      ' Error handling code, or Exception Handling code, basically the same thing.
      '
   Finally
      '
      ' Common block to perform some cleansing after the exception.
      '
   End Try
   '
   ' Code after the Try-End Try block
   '
End Sub

Any error outside the Try-End Try block will trigger an untrapped exception.  If the method (MyMethod) is outside a Try-End Try block itself, the exception will escalate, and if nobody catches it, your application will crash, returning an ugly message like this one:

 

Knowing this, you would try to put you code inside a Try-End Try block, wouldn't you? Well, there is another discussion on the internet regarding the usage of Try blocks, this one focusing on the performance impact of exception trapping in .NET. Once again, Barton Friedland's article has a nice explanation about the way exceptions are handled in the .NET Framework. A point not documented in these discussions is the overhead that occurs when your application triggers an exception.

Returning to the example, this time using more meaningful code,

 Private Sub Button4_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button4.Click

   Dim a As Integer
   Dim b As Integer = 5
   Dim c As Integer = 0

   Try

      a = b / c
   Catch e1 As SEHComponent.ErrorHandlingException
      ShowError(e1)
      'Catch se As SystemException
      ' MessageBox.Show(se.Message, "System Exception", _
      ' MessageBoxButtons.OK, _
      
' MessageBoxIcon.Error)
   End Try


End Sub

The Button4's click event will trigger an Arithmetic Exception because there is a division by zero.   The exception will go untrapped because the Catch we are using is expecting an SEHComponent.ErrorHandlingException exception.  Implementations like this will crash out with a message like this:

 

The Exception coded on the first (and unique) catch is not designed to process OverflowExceptions, We can improve the code by removing the comments for the second catch.

 

 

Page 2

Try
     a = b / c
Catch e1 As SEHComponent.ErrorHandlingException
   ShowError(e1)
Catch se As SystemException
   MessageBox.Show(se.Message, "System Exception", MessageBoxButtons.OK, _
   MessageBoxIcon.Error)
End Try

This time the OverflowException is trapped by the second catch, the SystemException one! This is happening because ALL the exception classes are children of the SystemException class, including the ApplicationException class.  For this simple reason, the code shown above will display the following error message to the user.

 

It looks nice and clean, no end user will be intimidated with a message like this.

POP-UP ERROR MESSAGES SIDE EFFECTS

A popup error message is a nice way to tell our application's user about any unexpected error in the application.  They are less frightening than those returned by the runtime.   Nevertheless, it has its shortcoming if your application is not recording key information about the exception at the time it happens and you are expecting the user to write the details down on a piece of paper.  In this case you should train him/her on what information is meaningful to you, like the message (Arithmetic operation resulted in an overflow), the popup form title (sometimes: System Exception) and the form he/she was working on when the exception happens (Form1). Sometimes they do a great job collecting this info for you, but there are risks you don't get the whole picture.

Your application is aware of a lot of things at the time an exception happens.  Wouldn't it be nice if you could write that information to a file or database, or even email it to you, instead of relying on the end user help. The message shown below is not very user friendly but it illustrates what exception classes could deliver as a whole into your applications.

Well, the message above tells us a lot of technical things when the error happened, but as previously said, it is not user friendly.   However you can write all this information to a file or database, including the date and time it happened.  You could even email it to yourself, or perhaps send an SMS message with all this information.

THE ErrorHandlingException CLASS


In the next section of this article we meet the ErrorHandlingException class, a class based on the ApplicationException class.   It has the following features.

  • Collect the date and time of the exception.
  • Identifies the Machine, Operating System, and User.
  • Identifies the application name, application version, culture and method.
  • Accept an Action parameter, where you can pass a more specific explanation of what your application was doing at the time of the exception.
  • It does not hide any existing property or method provided by its parent class: ApplicationException.
  • Write these exception's details to a log file or database (Access).

Beside the features listed above, you can expand it, implementing new functionalities, like:

  • Send emails or SMS messages containing the exception details.
  • You can easily write the exception's details to other database (MS SQL, Oracle)
  • Its project and source code is included, so you can enhance it to suit your requirements.

Now, we will walk thru the code for the ErrorHandlingException class, explaining its most relevant properties and methods. The project ErrorHandlingException (same name) contains the mentioned class, and it is assigned to the namespace SEHComponent (Structured Exception Handling Component).

Imports System.Data.OleDb
Imports System.Configuration.ConfigurationSettings

 

Page 3

The two imported namespaces are required by the methods handling data access and the application configuration file. You should import the namespaces for MS SQL and/or Oracle if you wish to extend this class by implementing either database to store the ExceptionLog data base.

<SERIALIZABLE()>Public Class ErrorHandlingException

   Inherits System.ApplicationException

The class declaration is preceded by the Serializable token and a couple of methods are required to implement its serialization.

Following Microsoft recommendation, it inherits the System.ApplicationException class, which enables the class to be used in exception trapping (ApplicationException trapping). The ApplicationException class exposes the following constructors, methods and properties:

Constructors:

Methods:

Properties:

Those with an asterisk (*) are the ones this implementation cater for by providing additional code. Beside the class members previously listed, we are also implementing these ones:

Custom Constructors:

  • ApplicationException Constructor (Method Action As String, message As String)
  • ApplicationException Constructor (Method Action As String, message As String, innerException)

Methods:

  • Private Clear()
  • Private GetApplicationAttributes()
  • Public GetExceptions()
  • Public Save(TargetErrorLog, TargetOutput)
  • Private SaveToFile(TargetOutput)
  • Private SaveToAccess(TargetOutput)

Properties:

  • ApplicationVersion
  • ApplicationCulture
  • ErrorDate
  • ErrorTime
  • MachineName
  • Method
  • MethodAction
  • OSVersion

Variables Declaration Region:
This is the first region in our class code.   Here all the private and public members required by the class' properties and methods are declared, as shown:

        Private _ObjectVersion As String = "0.0"
    Private bVersion As Boolean = False
    Private _ObjectCulture As String = "Unknown"
    Private bCulture As Boolean = False
    Private _MethodAction As String = "Unknown"
    Private _UserName As String = System.Environment.UserName
    Private _MachineName As String = System.Environment.MachineName
    Private _OSVersion As String = System.Environment.OSVersion.ToString
    Private _ErrorDate As Date
    Private _ErrorTime As TimeSpan

    Public Enum TargetErrorLogs
        FlatFile
        Access
    End Enum

    Const DEFAULT_CONNECTION_STRING As String = "DBConnStringErrorHandlingException"

You may notice that we are using the System.Environment namespace to obtain the user name, machine name and operating system version>   Your exception handling requirements could be different and so you may need to use other namespaces to implement additional properties.

Standard Constructors Region:
These are the constructors you may never have to change.

    Public Sub New()

        MyBase.New()
        Me.Clear()

    End Sub

    Public Sub New(ByVal message As String)

        MyBase.New(message)
        Me.Clear()

    End Sub

    Public Sub New(ByVal message As String, _
                   ByVal innerException As SystemException)

        MyBase.New(message, innerException)
        Me.Clear()

    
End Sub

There is nothing fancy in these constructors, perhaps the only one needed more explanation being the reference to the private method Clear() that is explained further down this post.

 

 

Page 4

De-Serialization Constructor
This is the constructor enforced by the Serializable token in front of the Class declaration.  It rebuilds all the class private members after a serialization and you may need to modify this constructors if you add or remove properties from this class.

    Public Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                   ByVal context As System.Runtime.Serialization.StreamingContext)
        '
        MyBase.New(info, context)

        _ObjectCulture = info.GetString("ApplicationCulture")
        _ObjectVersion = info.GetString("ApplicationVersion")
        bCulture = info.GetBoolean("ApplicationCultureFlag")
        bVersion = info.GetBoolean("ApplicationVersionFlag")
        _MethodAction = info.GetString("MethodAction")
        _MachineName = info.GetString("MachineName")
        _UserName = info.GetString("UserName")
        _OSVersion = info.GetString("OSVersion")
        _ErrorDate = info.GetDateTime("ErrorDate")
        _ErrorTime = DirectCast(info.GetString("ErrorTime"), TimeSpan)

    
End Sub

Custom Constructors Region:
Custom constructors are an important factor when inheriting from the ApplicationException class.   Here is where you implement additional properties to your particular exception handling class and you may add as many as your solution requires.

We are introducing the MethodAction property as one of those additional properties this exception class supports.  When you throw an exception you can pass a message that explains what your code was trying to do.  If your method has to trigger more than one exception, your message should include some kind of description of the action takeing place when the error condition was found.   That is precisely why the MethodAction property came to life.   Developers working with this exception class can pass this additional information without messing too much with the exception's message.

The next two constructors provide support for the MethodAction property.

    Public Sub New(ByVal MethodAction As String, _
                   ByVal message As String)

        MyBase.New(message)
        _MethodAction = MethodAction

    End Sub

    Public Sub New(ByVal MethodAction As String, _
                   ByVal message As String, _
                   ByVal innerException As SystemException)

        MyBase.New(message, innerException)
        _MethodAction = MethodAction

    End Sub

As you can see, each one of these constructors takes a MethodAction parameter, which is assigned to the private member _MethodAction.   Later on the class exposes its value with the MethodAction property. I hope that the chosen names do not confuse you, as a parameter was named MethodAction, and a property goes with the same name.sad

The first constructor takes just the message parameter for the exception, while the second handles two, the message and the innerException.  This is a standard practice when implementing additional parameters into exception classes.

Properties Region:
Now is time to show the region where the properties' declaration takes place in this class.  The first thing to notice is that all these properties are Readonly. You don't want any developer working with your exception class to tamper with the exception attributes.

We already explained that the machine name, operating system version and user name properties come from the System.Environment name space.   What about the remaining properties, how are they coming to life? Well, the private method GetApplicationAttributes() takes care of them.

    Public ReadOnly Property ApplicationVersion() As String
        Get
            If bVersion = False Then
                GetApplicationAttributes()
            End If
            Return
_ObjectVersion
        End Get
    End Property

    Public
ReadOnly Property ApplicationCulture() As String
        Get
            If
bCulture = False Then
                GetApplicationAttributes()
            End If
            Return
_ObjectCulture
        End Get
    End Property

    Public
ReadOnly Property ErrorDate() As String
        Get
            Return
_ErrorDate.ToString("yyyy/MM/dd")
        End Get
    End Property

    Public
ReadOnly Property ErrorTime() As String
        Get
            Return
_ErrorTime.Hours.ToString & ":" & _ErrorTime.Minutes.ToString
        End Get
    End Property

    Public
ReadOnly Property MachineName() As String
        Get
            Return
_MachineName
        End Get
    End Property

    Public
ReadOnly Property Method() As String
        Get
            Return
MyBase.TargetSite.Name
        End Get
    End Property

    Public
ReadOnly Property MethodAction() As String
        Get
            Return
_MethodAction
        End Get
    End Property

    Public
ReadOnly Property OSVersion() As String
        Get
            Return
_OSVersion
        End Get
    End Property

    Public
Overrides Function ToString() As String

        Return
MyBase.ToString & vbCrLf & _
               "Source Object : " & Me.Source & vbCrLf & _
               "Source Method : " & Me.TargetSite.Name & vbCrLf & _
               "Method Action : " & _MethodAction

    End Function

    Public
ReadOnly Property UserName() As String
        Get
            Return
_UserName
        
End Get
    End Property

Take a look at the ToString function.   Yes, it is listed as a property, but readonly properties and functions are closely related.   A function behaves like a Readonly property and vice versa. The first thing to notice is: It is incomplete, there are properties missing, you can add them later, We didn't because we were implementing additional functionalities to the class, but the idea with the ToString function is that it should return all the class private members.

 

 

Page 5
The Methods region:
The class implements seven methods.   We will go through them now.

Private Clear method:

     Private Sub Clear()

        _ObjectVersion = "0.0"
        _ObjectCulture = "Unknown"
        _MethodAction = "Unknown"
        _UserName = System.Environment.UserName
        _MachineName = System.Environment.MachineName
        _OSVersion = System.Environment.OSVersion.ToString
        bVersion = False
        bCulture = False

        GetApplicationAttributes()

    
End Sub

The Clear method is Private because you don't want developers working with your class to wipe out the exception attributes.  The method is referenced by the constructors at the time they are initializing the class. If you add new Private members to the class you should also initialize them here. You may think this Clear method is redundant because private member are already initialized when you declare them.   However including their initialization here will make sense and future developers taking over the responsibility of maintaining this code should understand it easier.

There is a reference to the mystery method GetApplicationAttributes(), we will get to it very shortly.

The GetObjectData Method:
This method is the one de-serializing this class.   Due to its function it is a very important one.   If you are not careful coding it, the class may have serialization problems.  The main goal when de-serializing and serializing objects is their private members values do not change or get lost.    In other words, its state should not change.

     Public Overrides Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _
                                         ByVal context As System.Runtime.Serialization.StreamingContext)

        MyBase.GetObjectData(info, context)

        info.AddValue("ApplicationCulture", _ObjectCulture)
        info.AddValue("ApplicationVersion", _ObjectVersion)
        info.AddValue("ApplicationCultureFlag", bCulture)
        info.AddValue("ApplicationVersionFlag", bVersion)
        info.AddValue("MethodAction", _MethodAction)
        info.AddValue("MachineName", _MachineName)
        info.AddValue("UserName", _UserName)
        info.AddValue("OSVersion", _OSVersion)
        info.AddValue("ErrorDate", _ErrorDate)
        info.AddValue("ErrorTime", _ErrorTime)

    
End Sub

The method is public and overrides the base class GetObjectData method, and then it de-serializes the base class with this line of code

 MyBase.GetObjectData(info, context)

The info object contains the de-serialized base class.   Now you should add all your new private members to the info object and you will be done.

If you are expanding this class by adding new properties, you should include their private member in this method as well.

Private method GetApplicationAttributes:
If you are familiar with the Gang of Four (GoF) design patterns, you may think this class implements a Facade Design Pattern for the application exception class.   The GoF quote below is taken from their literature.

 

 GoF Intent for Facade: Provide a unified interface to a set of interfaces in a subsystem. The Facade defines a higher-level interface that makes the subsystem easier to use.

 

The GetApplicationAttributes method enforces this approach; it drills deep into the exception objects trees, finding attributes later exposed as properties by the class.

     Private Sub GetApplicationAttributes()

        If Not Me.TargetSite Is Nothing Then
            If Not Me.TargetSite.ReflectedType Is Nothing Then
                If Not Me.TargetSite.ReflectedType.AssemblyQualifiedName Is Nothing Then

                    Dim s() As String = Split(Me.TargetSite.ReflectedType.AssemblyQualifiedName)
                    Dim s1 As String

                    For Each s1 In s
                        If bVersion = False Then
                            If s1.IndexOf("Version", 0) >= 0 Then
                                Dim s2() As String = Split(s1, "=")
                                _ObjectVersion = s2(1)
                                If _ObjectVersion.IndexOf(",") >= 0 Then
                                    _ObjectVersion = _ObjectVersion.Substring(0, _ObjectVersion.IndexOf(","))
                                End If
                                bVersion = True
                            End If
                        End If
                        If
bCulture = False Then
                            If
s1.IndexOf("Culture", 0) >= 0 Then
                                Dim
s2() As String = Split(s1, "=")
                                _ObjectCulture = s2(1)
                                If _ObjectCulture.IndexOf(",") >= 0 Then
                                    _ObjectCulture = _ObjectCulture.Substring(0, _ObjectCulture.IndexOf(","))
                                End If
                                bCulture = True
                            End If
                        End If
                        If
bVersion = True And bCulture = True Then
                            Exit For
                        End If
                    Next
                End If
            End If
        End If

        _ErrorDate = Now.Date
        _ErrorTime = Now.TimeOfDay

    
End Sub

The method is working with the AssemblyQualifiedName property found at the ReflectedType member, which is located inside the exception's TargetSite. All the Is Nothing validations are required by the way exception objects behave  - more on that later on, in the post explaining How to use this ErrorHandlingException class.

The AssemblyQualifiedName property contains the executable's version number and its culture.   Finding and exposing them will make developers' life working with this class easier.

If the implementation approach used to build this class is clear to you, you may want to use this method to find and expose additional information about the exception being handled.

The Public Save() method:
Now that this ApplicationExecption class collects a lot of information about the exception being handled, the Save() method allows developers implementing it to "save" the exception details to an error log object. The class already implements two of these error log objects; a flat text file or an Access Database; you can extend this class to implement "additional" error log objects.

    Public Sub Save(ByVal TargetErrorLog As TargetErrorLogs, _
                    ByVal TargetOutput As String)
        '
        ' Save exception details to log table in ApplicationException database
        '
        If bVersion = False OrElse bCulture = False Then
            GetApplicationAttributes()
        End If

        Select Case
TargetErrorLog
            Case TargetErrorLogs.FlatFile
                SaveToFile(TargetOutput)

            Case TargetErrorLogs.Access
                SaveToAccess(TargetOutput)

        
End Select

    End Sub

It takes two parameters:

TargetErrorLog: This is of a TargetErrorLogs type, telling the method the procedure to follow to "save" the exception details. The type was declared at the top of the class:

    Public Enum TargetErrorLogs
        FlatFile
        Access
    End Enum

Currently defining two values: FlatFile and Access. Developers working with the class can only chose two target error log objects as defined by this enumeration.

If you want to extend this class to "save" the exception details to a new error log object, then you should modify this enumeration adding its associated key-word name.

TargetOutput: If the target error log object is a flat file you should pass its name thru this parameter.   If the target is a database, then the connection string "key" as defined in the application config file should be passed instead. If you extend this class to email the exception details then this parameter is likely to receive a distribution list of the emails recipients for the email. The logic within the Save() and its associated "SaveTo ..." methods deals with an empty TargetOutput value.

The next thing the Save method does is check for the application version and culture.   If they are unknown the GetApplicationAttributes() is invoked, making sure the class state is up to date.

Finally, the Save method sorts out the target error log object to use based on the TargetErrorLog parameter received.

This time we are using a sort of GoF Factory Design Pattern. Each target error log object has a private "SaveTo ... " method, because you don't want developers working with this class to implement different "SaveTo ..." methods thru the application.  Besides,  sticking with the Save() factory method will make it a lot easier to switch from writing to a database error log into emailing the exceptions details. An entry in the application config file will do so without recompiling your application.

 

Page 6

Private SaveToFile() method:
This is the method writing the exception details to a flat file that developers provide when calling the Save() method or to the default file coded in the class.

     Private Sub SaveToFile(ByVal TargetFile As String)

        If TargetFile.Length = 0 Then
            TargetFile = "C:\Temp\ApplicationErrors.log"
        Else
            If
TargetFile.IndexOf(":\") < 0 Then
                TargetFile = "C:\Temp\" & TargetFile
            End If
        End If

        Dim
OutputFile As New System.IO.StreamWriter(TargetFile, True)
        Dim sOutput As String

        sOutput = Me.ErrorDate & "/"
        sOutput &= Me.ErrorTime & "/"
        sOutput &= Me.MachineName & "/"
        sOutput &= Me.OSVersion & "/"
        sOutput &= Me.UserName & "/"
        sOutput &= Me.Source & "/"
        sOutput &= Me.ApplicationVersion & "/"
        sOutput &= Me.ApplicationCulture & "/"
        sOutput &= Me.Method & "/"
        sOutput &= Me.MethodAction & "/"
        sOutput &= Me.Message & "/"
        sOutput &= Me.StackTrace.Trim

        OutputFile.WriteLine(sOutput)
        OutputFile.Close()

TargetFile: This parameter contains the name and location of the file where the exception details will be written.

The first thing the method does is making sure a file name is provided, if the parameter is empty, it assigns the default target flat file: C:\Temp\ApplicationErrors.log, otherwise, it checks the given file name for a drive name sign within the name, searching for ":\" does the trick, if no evidence of a drive name is found, the code insert the folder "C:\Temp\".

WARNING: This approach has its weakness, and you may want to improve it.

  1. The log file could be at each user workstation's C:\Temp folder.
  2. Users' workstations C: drive could be hidden.
  3. The drive validation seems poorly implemented, what about a network drive name?

The method keep going by opening the TargetFile, building a long string with all the exception details, separating them with pipes, appending the long string to the file and finally closing the TargetFile.

Private SaveToAccess() Method:
This method write our application's exception details to an Access Database, the access database is accessed thru a connection string, so as long as it is reachable we have nothing to worry about.

     Private Sub SaveToAccess(ByVal ConnectionStringKey As String)

        Dim SQL_Connection As New OleDbConnection
        Dim SQL_Command As New OleDbCommand
        Dim sSQL_Command As String = ""
        '
        ' Retrieve the connection string, from config file, pass it to the command object
        
'
        If ConnectionStringKey.Length = 0 Then
            ConnectionStringKey = DEFAULT_CONNECTION_STRING
        End If

        SQL_Connection.ConnectionString = AppSettings.Get(ConnectionStringKey)
        SQL_Command.Connection = SQL_Connection
        '
        ' Build the INSERT command
        '
        sSQL_Command = "INSERT INTO ErrorLog ( " & _
                       " [ErrorDate] " & _
                       " , [ErrorTime] " & _
                       " , [Machine] " & _
                       " , [OSVersion] " & _
                       " , [User] " & _
                       " , [Application] " & _
                       " , [Version] " & _
                       " , [Method] " & _
                       " , [Action] " & _
                       " , [Message] " & _
                       " , [ErrorStack] " & _
                       ") " & _
                       "VALUES ( " & _
                       " @ErrorDate " & _
                       " , @ErrorTime " & _
                       " , @Machine " & _
                       " , @OSVersion " & _
                       " , @User " & _
                       " , @Application " & _
                       " , @Version " & _
                       " , @Method " & _
                       " , @Action " & _
                       " , @Message " & _
                       " , @ErrorStack " & _
                       ")"
        '
        ' We don't have to worry about apostrophes in the fields we are saving to the database
        ' because we are passing their values as parameters, and its add method takes care of them.
        '
        With SQL_Command
            .Parameters.Add("@ErrorDate", Me.ErrorDate)
            .Parameters.Add("@ErrorTime", Me.ErrorTime)
            .Parameters.Add("@Machine", Me.MachineName)
            .Parameters.Add("@OSVersion", Me.OSVersion)
            .Parameters.Add("@User", Me.UserName)
            .Parameters.Add("@Application", Me.Source)
            .Parameters.Add("@Version", Me.ApplicationVersion)
            .Parameters.Add("@Method", Me.Method)
            .Parameters.Add("@Action", Me.MethodAction)
            .Parameters.Add("@Message", Me.Message)
            .Parameters.Add("@ErrorStack", Me.StackTrace.Trim)

            .CommandText = sSQL_Command
        End With
        '
        ' We are now ready to go
        '
        Try
            '
            SQL_Connection.Open()
            SQL_Command.ExecuteNonQuery()
        Catch ex As OleDbException
            '
            ' We got an error!!! saving the exception to file instead.
            '
            SaveToFile("")
            '
        End Try
        '
        ' Cleaning up
        '
        SQL_Command.Dispose()
        SQL_Connection.Close()
        SQL_Connection.Dispose()
        '
    
End Sub

ConnectionStringKey: This parameter pass the connection string "key" as defined at the application's config file, for the database we are using. The sample application attached (Test_ErrorHandlingException) defined it as shown:

<appSettings>
   <!-- User application and configured property settings go here.-->
   <!-- Example: <add key=
"settingName" value="settingValue"/> -->
   <add key="DBConnStringErrorHandlingException" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Temp\TargetErrorLog.mdb" />
</appSettings>

Because, it is a connection string, you should replace it with the appropriated entry for your database location; the connection string key name is: DBConnStringErrorHandlingException, and the class uses it as a default when an empty connection string key is passed.

The connection string uses the access database TargetErrorLog.mdb located at the C:\temp folder, the database name could be anything, and there are no restrictions, the same goes with the folder C:\temp.

As we are retrieving the connection string from the application's config file, we should include the namespace: Imports System.Configuration.ConfigurationSettings at the top of the class file, as previously explained.

WARNING: Keep in mind the SaveToAccess method lacks an strong validation for a missing connection string, you should make sure the application's config file has its entry before playing around with this class.

 

Page 7

Now that most of the disclaimers and explanations for the connection string are covered, let's move on with the method's remaining logic. It is nothing but a plain vanilla INSERT INTO method.  It is expecting the table ErrorLog.  This table is defined with the following columns:



There is a column per each member of the Exception class we want to save into the error log. If you want to implement a different data base like MS SQL or Oracle, you should create a table named ErrorLog with the same columns, although Oracle is a little bit tricky handling the Auto Increment column.

All the columns from the ErrorLog table are surrounded with brackets to avoid reserved words exceptions. We are using parameters to assign values to each of the table's columns; it is safer this way as we don't have to worry about apostrophes in the data.

The method relies on the OleDB namespace, so we are importing it at the top of the class file, as previously explained.

TIP: If you expand this class to support MS SQL or Oracle, you should import the appropiate namespaces.  The INSERT INTO command is basically the same for Access and MS SQL, but with Oracle you should replace the '@' charater with ":" (colons) as that is Oracle's preferred parameters leading character.

Public GetExceptions() Method:
This method returns a data table that you can bind to a data grid, providing an easy way to look at all the exceptions written to the ErrorLog table.

     Public Function GetExceptions(ByVal ConnectionStringKey As String) As DataTable

        Dim SQL_Connection As New OleDbConnection
        Dim SQL_Command As New OleDbCommand
        Dim sSQL_Command As String = ""
        Dim SQL_Adapter As New OleDbDataAdapter
        Dim dt As New DataTable
        '
        ' Retrieve the connection string, from config file, pass it to the command object
        '
        If ConnectionStringKey.Length = 0 Then
            ConnectionStringKey = DEFAULT_CONNECTION_STRING
        End If

        SQL_Connection.ConnectionString = AppSettings.Get(ConnectionStringKey)
        SQL_Command.Connection = SQL_Connection

        sSQL_Command = "SELECT * FROM ErrorLog"
        SQL_Command.CommandText = sSQL_Command

        Try
            SQL_Adapter.SelectCommand = SQL_Command
            SQL_Adapter.Fill(dt)
            GetExceptions = dt

        Catch exc As OleDbException
            GetExceptions = Nothing

        End Try

        dt.Dispose()
        SQL_Adapter.Dispose()
        SQL_Command.Dispose()

        SQL_Connection.Close()
        SQL_Connection.Dispose()
        SQL_Connection = Nothing
        SQL_Adapter = Nothing

    
End Function

ConnectionStringKey: This parameter has the same scope as the one used at the SaveToAccess method, all the disclaimers and explanations written before.

This method is good to obtain all the exception details from an Access database, small changes are required when dealing with MS SQL and Oracle.

REFERENCES:
ApplicationException Class by MSDN.
ApplicationException Members by MSDN.
.NET Anatomy - Structured Exception Handling in .NET by Barton Friedland.
Exception classes in .NET Framework
Exceptions by Terry Smith.
The Well-Tempered Exception by Eric Gunnerson at Microsoft Corporation
How to write custom exception classes in C#

DISCLAIMER
The code is posted and attached as is, feel free to use it, we advise that you should get acquainted with it before implementing this class in your applications.

Its main weakness is with the WriteToFile method; you should review and perhaps enhance it, driven by your organization system security rules.

The SaveToAccess() method does not have exception trapping and connection string key validations.  You may add this validation or go ahead knowing the slim chance it may break at those two danger points.
ATTACHED PROJECT
The attached zip file contains the class project ErrorHandlingExceptions.   Its Data folder contains the access data base the test project uses to write the exceptions details.  You should either copy this Access database to your C:\Temp folder, or amend the connection string making it to point to the folder of your preference.

The project is written in VB.Net 2003.

 

 

 

Using ErrorHandlingException Class (i)
HOW TO USE THE ErrorHandlingException CLASS:
This topic explains with a test project (attached) how to use the ErrorHandlingException class described within this thread.

REFERENCE TO THE ErrorHandlingException CLASS:
The first thing you should do before using the ErrorHandlingException class described above is adding a reference of its DLL to the project where you want to use it.


 
  1. Expand your project's tree in the solution explorer.
  2. Right click the References node.
  3. Click on the Add Reference option.
  4. Click the Browse button.
  5. Navigate to the folder where the file ErrorHandlingException.dll is located.
  6. Select the file.
  7. Click the Open button.
  8. Click the Ok button.

 All set, you will be able to use the ErrorHandlingException class through your project's code.

REQUIRED CONNECTION STRING AT YOUR PROJECT app.config:
If you want to record your application's exceptions to an ErrorLog table as described in the post explaining the Class, its config file should have an entry like this one.

<?xml version=<?xml version="1.0" encoding="utf-8"?>
<CONFIGURATION>
   <APPSETTINGS>
      
      
      <ADD class=s color="#ff00ff" key="" font DBConnStringErrorHandlingException?<> value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Temp\TargetErrorLog.mdb" />
   </APPSETTINGS>
</CONFIGURATION>
</APPSETTINGS>

Where the key value should be DBConnStringErrorHandlingException which is the default expected entry, or the one of your preference, while the value should point to the location of the Access database you will be using to keep your Exceptions log.

A similar approach of creating a config entry is suggested when you expand the class to save your exception to a database other than access.

On Error Resume Next EQUIVALENT USING TRY-END TRY BLOCKS:
The Try-End Try equivalent of the On Error Resume Next statement is achieved by implementing an empty CATCH block, as shown:

Dim a As Integer
Dim b As Integer = 5
Dim c As Integer = 0

Try
   a = b / c
Catch ex As Exception

End Try
'
' more code
'

The above sample code illustrates the use of a Try-Catch-End Try block emulating the On Error Resume Next statement; the error inside the Try block will not stop the method execution.

THROWING DATA DRIVEN EXCEPTION:
You application is expected to throw an ErrorHandlingException when it finds an error condition with the data your application is handling.  It should do something like this:

   Try
      Dim
a As Integer
      Dim b As Integer = 5
      Dim c As Integer = 0
      '
      If c <> 0 Then
         a = b / c
      Else
         Throw New
SEHComponent.ErrorHandlingException( _
                                 "Doing some calculations", _
                                 "Invalid parameter")
      End If
   Catch
ehe As SEHComponent.ErrorHandlingException
        ehe.Save(SEHComponent.ErrorHandlingException.TargetErrorLogs.Access, "")
   End Try

The example code shows that the application identifies a data error condition, throwing the ErrorHandlingException.  The Try block catch it with the variable ehe and Saves its details to the target access database without showing any warning message to the user.

It will be up to the application logic to determine what to do when the error condition is found after catching the thrown exception.

HANDLING SYSTEM EXCEPTIONS:
Our ErrorHandlingException class is cool to trap any exception thrown by the application upon performing data validation, but it will completely miss any system generated exception.   The sample code below illustrates the latter fact:

 Try
   '
   ' Validating data errors and throwing an ErrorHandlingException
   '
   If c <> 0 Then
      a = b / c
   Else
      Throw New SEHComponent.ErrorHandlingException("Division", _
                         "Trying to divide by zero")
   End If
   '
   ' Trapping exceptions, other than ErrorHandlingException
   ' and re-routing it thru its class
   '
   a = b / (d * c) ' <====
   '
Catch ehe As SEHComponent.ErrorHandlingException
   ehe.Save(SEHComponent.ErrorHandlingException.TargetErrorLogs.Access, "")
Catch se As SystemException
   MessageBox.Show(se.Message, _
                   "System Exception", _
                   MessageBoxButtons.OK, _
                   MessageBoxIcon.Error)
End Try

The exception thrown by this code: a = b / (d * c) will be trapped by the SystemException instead. This is true for any Exception your code may throw.

What you should do to get these excections handled by the ErrorHandlingException is redirect the System Exceptions, as shown.

 Try
   '
   ' try block
   '
   a = b / (d * c) ' <====
   '
Catch se As SystemException
   Try
      Throw New SEHComponent.ErrorHandlingException("Re-routing it", _
                             se.Message, _
                             se)
   Catch ehe As SEHComponent.ErrorHandlingException
      ShowError(ehe)
   End Try
End Try

 

 

 

Using the ErrorHandlingException Class (ii)

This time the SystemException Catch block is throwing an Application Exception using our class and passing the System Exception message se.Message and the exception itself as inner exception se to the ErrorHandlingException class   By doing this, we can trap the application exception using the Catch ehe As SEHComponent.ErrorHandlingException code.

The method ShownError() is part of the demo project.    You can check it out by downloading the attached project to this article.   It basically display a pop-up window describing the exception.

Imports SEHComponent
The code snippets shown so far feature reference to the ErrorHandlingException class by fully qualifying its namespace, but you can avoid too much typing by importing its name space with this line of code at the top of the project using it:

 Imports SEHComponent

Once this is done, the throw and catch statements referencing it become:

Throw New ErrorHandlingException("Division", _
                                 "Trying to divide by zero")

and / or

Catch ehe As ErrorHandlingException

ENQUIRIES TO THE EXECEPTION LOG
The ErrorHandlingException class as explained on the first and second pages of this article expose a GetExceptions() method returning a DataTable object.    You can use this method to provide a quick way to view all the exceptions your application is throwing by adding a data grid to a Windows form or Web Page.   As the class does not have a presentation layer, you can use it on either Windows or Web applications.

The image below illustrates the enquiry we are talking about.



The image identifies the application as Test_ErrorHandling but because posting that project into this article was not possible we renamed the application to TestEHE.

The behind the View Exceptions button shown in the picture follows:

Dim SEH As New ErrorHandlingException
Dim dt As DataTable

dt = SEH.GetExceptions("")

If Not dt Is Nothing Then

   dgExceptions.DataSource = dt
   dgExceptions.ReadOnly = True

End If

The GetExceptions method accepts a single parameter, the connection string for the database we are using to Save our exceptions errors.   As we are passing an empty string, the class will use the default connection string key, and retrieve the connection string defined at the Application Config file as documented above.

PRJECT TestEHE
The project used to test the ErrorHandlingException class is attached to this post.   You can download it to get a better understanding on how to use the class and get familiar with its behavior.



Its Plain Untrapped Exception and Plain Untrapped Exception (Using Imports) buttons do not display any pop-up message, they just save the exceptions to the Access database.

You should copy the Access database TargetErrorLog.mdb located at its Data folder to the C:\Temp folder once you unzip the project, or fix the Application Config file to point to its location on your PC.

The project has a reference to the ErrorHandlingException.dll binary.   This reference might be broken once you unzip the projects posted on this thread and you should fix the reference before trying to use it.

CONCLUSIONS

  • You can extend the ApplicationException class provided by the .Net Framework, implementing additional functionalities that certainly will benefit your application.
  • You can use any .Net development language to implement your ApplicationException class, either C# or VB.Net without major concerns, as you can use your customized exception handling object in any project regardless of the language of preference at your organization.
  • You may extend the System Exception class, but it is Microsoft remendation that any extention to Exception classes, be done off the Application Exception class.