DevCity.NET -
Exception Handling in Enterprise Applications
Scott Rutherford
Scott Rutherford is a consultant and .NET developer who has developed Enterprise business applications in many fields including Restaurant, Market Research, Automotive, and Analytical Chemistry. He is an Microsoft Certified Professional with the .NET Platform. 
by Scott Rutherford
Published on 2/28/2005
This article includes eight suggestions for Best Practices in exception handling in .NET, with code examples.  Learn how to properly catch, identify, raise, log, and respond to errors encountered by applications of all types.

This article assumes a general understanding of Structured Exception Handling in .NET (VB or C#).
This article is supplemental to the MSDN article: Best Practices for Handling Exceptions (read this!!!).

Exception handling is often understood to mean "exception stopping". That is, make my program continue to run without stopping and without requiring human interaction. However, exceptions can be lifelines that help you maintain the health of a dynamic Enterprise application. Often you will want to track and respond to an application that is not self-contained in a single process, or on a single machine.

In this article, I suggest eight "Best Practices" to implement in Enterprise Applications, with code examples to show how it's done. If you consider the suggestions below early in the development process, and follow these practices in all applications, you will reap the benefits on the Enterprise scale.

  1. Always include the Source Exception, using InnerException
  2. Uniquely Identify error locations in your code
  3. Identify the Importance of the error
  4. Act on the Urgency of the error
  5. Handle errors Centrally, but use available Context
  6. Report errors to Windows Event Logs
  7. Ensure all apps can Write to Windows Event Logs
  8. Use Enterprise Tools to identify program errors

The basic rule is to ensure your exceptions are raised with codes that are programmatically, or at least systematically, readable. And always use the Windows Event Logs to report errors.  This will allow you to use Enterprise tools to monitor and respond to logged errors. The main benefit of following these practices is that all program errors are centrally reported and uniquely identified. This gives Enterprise administrators the power to identify and respond to all program errors.

    Here are some examples of real life applications in which I implement these rules:
  • An ASP.NET application that opens a window to the Enterprise for outside clients
  • Various desktop apps
  • Windows Service that picks up data files from legacy apps at remote sites
  • Data broker component that provides database access in various ASP.NET applications
  • Windows Service that monitors processes and log files running on remote machines


Suggestions 1-4

1. When throwing exceptions, always include the source exception if one exists, by setting the InnerException property.

You never know how an exception is going to be dealt with at higher levels, so it is best to provide as much information as possible regarding the state and cause of the exception.

[Visual Basic]
        Catch e As Exception
            Throw New ApplicationException("Uh oh...", e)
        End Try 

Practice 5, below, shows you how to access and use this InnerException at higher levels

2. Uniquely identify error locations in code, and group into types.

This will allow a link from the ultimate destination of the error message (e.g. the Event Log) back to source code. The simplest way to do this is to share an enum with all source code that provides a code lookup, which I call EventID:

[Visual Basic]
    Public Enum EventID As Integer
        DATA_ERROR = 1
        FTP_UNKNOWN = 5
    End Enum 

3. Identify the importance of the error

Basically, you need to decide if the error encountered is going to stop the program from doing what it is expected to do. There are three possible values that indicate error importance: critical, warning, and information. Again, the simplest way to identify this is to share an enum, which I call EventImportance:

[Visual Basic]   
 Public Enum EventImportance As Short
        Critical = 1
        Information = 2
        Warning = 3
    End Enum
Here is an example of the distinction:
A network error is encountered because a required path cannot be found. This could mean that the task will never be completed because the application does not have access to read the directory, in which case the error is critical. It could also mean that the operation timed out because of heavy traffic but would presumably succeed on a subsequent pass, in which case the error is a warning (perhaps something could be done administratively to resolve the issue, perhaps not). However, the path may be unavailable simply because it has been removed and is no longer required, in which case the error is purely informational.

Note: you could use the .NET system enumeration System.Diagnostics.EventLogEntryType here, although it might be more flexible to support your own custom enumeration.

4. Act according to the urgency of the error

Urgency is whether or not the program can continue to function in light of the error encountered. This is generally independent of the importance, and it is this issue that will dictate how you handle the error.

  • If an error is urgent, it needs to be raised so that the current process stops, or at least handles it at a higher level.
  • If not, it can be logged (according to its importance) and processing can continue.


Suggestions 5-7

5. Centralize the handling of errors, but make use of all contextual information.

Putting all of the above practices together gives you a richer context in which to report errors. The actual logic of reporting the error is something that you can implement in a centralized way. In this code example, a critical exception is caught. It is then passed to the central method called HandleError() to be read and logged. HandleError() subsequently calls LogEvent(), which is discussed in #6, below.

[Visual Basic]       
            Dim sPath As String = "\\MYSERVER\myshare\myfile.txt"
        Catch ex As System.IO.FileNotFoundException
            HandleError(ex, EventImportance.Critical, EventID.FILE_ACCESS_ERROR, _
                              "[Delete failed: can't access path.]")
        End Try
    Public Sub HandleError(ByRef e As Exception, ByVal Importance As EventImportance, _
                                                          ByVal ID As EventID, ByVal ExtraInfo As String)
        Dim sMessage As String
        While Not e.InnerException Is Nothing
            e = e.InnerException
            sMessage += Chr(13) + e.Message + " [" + e.GetType().ToString() + "]"
        End While
        If ExtraInfo <> "" Then
            sMessage += Chr(13) + ExtraInfo
        End If
        LogEvent(sMessage, Importance, ID)
    End Sub 

6. Report application errors to the Windows Event Logs

The Windows Event Logs provide an existing infrastructure that can be leveraged in the task of Enterprise error handling. It provides the same reliable error logging as used by the OS, and additionally offers sorting, filtering, and growth control of logs.

[Visual Basic]
    Private Sub LogEvent(ByVal Msg As String, ByVal Type As EventImportance, ByVal ID As Integer)
        Dim sEventLogName = "Application"
        Dim elog As New System.Diagnostics.EventLog(sEventLogName)
        elog.Source = GetAssemblyName()
        If AlreadyLogged(elog, ID, Msg) Then Exit Sub
        Dim EventType As System.Diagnostics.EventLogEntryType
        Select Case Type
            Case EventImportance.Critical
                EventType = System.Diagnostics.EventLogEntryType.Error
            Case EventImportance.Information
                EventType = System.Diagnostics.EventLogEntryType.Information
            Case EventImportance.Warning
                EventType = System.Diagnostics.EventLogEntryType.Warning
        End Select
            elog.WriteEntry(Msg, EventType, ID)
        Catch ex As System.ComponentModel.Win32Exception
            WriteFile("Windows Event Log [" + sEventLogName + "] is full!  " + GetAssemblyName() _
                    + " application failed to write error " + ID.ToString + ": " + Msg)
        Catch ex As Exception
            WriteFile("Unknown error writing to Windows Event Log [" + sEventLogName + "]!" + _
                    ex.Message + GetAssemblyName() + _
                    " application failed to write error " + ID.ToString + ": " + Msg)
        End Try
    End Sub
    Private Function GetAssemblyName() As String
        Dim assemb As Reflection.Assembly = Reflection.Assembly.GetExecutingAssembly()
        Dim attr As System.Attribute
        For Each attr In assemb.GetCustomAttributes(False)
            If TypeOf attr Is System.Reflection.AssemblyTitleAttribute Then
                GetAssemblyName = CType(attr, System.Reflection.AssemblyTitleAttribute).Title
            End If
    End Function
    Private Function AlreadyLogged(ByRef Log As System.Diagnostics.EventLog, _
                                   ByVal ID As Integer, ByVal Message As String) As Boolean
        Dim entr As System.Diagnostics.EventLogEntry
        AlreadyLogged = False
        For Each entr In Log.Entries
            If entr.EventID = ID And entr.Message = Message Then
                AlreadyLogged = True
                Exit Function
            End If
    End Function 

7. Ensure all apps are able to report to the Windows Event Logs

With Windows 2003, security on the Event Logs has changed. You can now define custom Security Descriptors for each log. For example, the following registry key value defines access to the Application Event Log:

You need to configure carefully the various event logs used by your program so that it can log properly to the Event Log in all instances, and under the security context of any potential user. You also need to ensure all users have access to read the above registry key or you won’t get far.
    At the end of this value are tokens such as:
  • (A;;0x1;;;DU) - allow domain users to read this event log
  • (D;;0x7;;;BG) - deny built-in guest users (e.g. IUSER, ASPNET user) read, write, and clear
  • (A;;0x3;;;IU) - allow interactive users to read and write to it
    The middle unit of each token is a bitwise AND, in hex format, of 1:read, 2:write, 4:clear.

    Other possible values for the last unit of each token include:
  • BA (Built-in admin)
  • SY (System)
  • AN (Anonymous)
  • SO (Server operators)
  • SU (Service users)
  • any SID representing a specific user or group on the machine or domain.


Suggestion 8, References and Links

8. Use enterprise tools to identify and respond to program errors

There are several tools available for monitoring your Event Log remotely, so keeping tabs on multiple servers is not a problem. One such program with which I've had success is Quest Software's Big Brother. A small windows service runs on each server and monitors a multitude of factors including Event Log messages. Reporting of errors can trigger an email or simply update a dashboard display on a single Big Brother server.

Of course you can monitor Event Logs manually across the network using MMC, or programmatically with WMI as well.


.NET Framework Developer's Guide: Best Practices for Handling Exceptions

Microsoft Knowledge Base Article - 315965
HOW TO: Use Structured Exception Handling in Visual Basic .NET;en-us;Q315965&ID=kb;en-us;Q315965&SD=MSDN

.NET Framework Developer's Guide: Handling and Throwing Exceptions

Choosing When to Use Structured and Unstructured Exception Handling

Development Impacts of Security Changes in Windows Server 2003 (see Tighter ACLs on Event Logs)

Big Brother -

Monitoring in .NET Distributed Application Design