Update: A C# version of this base exception class is posted here.
This is an update to my previous post about a custom base exception class. I spent some time in the interim reviewing the Microsoft Exception Management Application Block code and added some of the features that I found there. Reassuringly, most of my design features matched and in some cases exceeded theirs. Here is a listing of changes since that earlier version:
- Added custom properties for the Windows Identity and an Additional Information collection. This included the serialization, deserialization and ToString code.
- Added error handling to the code which obtained the custom variable information. This was particularly for security exceptions.
- Added a security attribute to the GetObjectData routine.
- Changed most properties from read-write to read only since they should never be changed.
- Changed the Integer properties to String.
Hopefully, I am finished this superset of base exception class features. If I think of something more I need I will add it, but I believe that this will be sufficient for my base exception class requirements.
It was evident that Google crawled GeeksWithBlogs a couple of days ago since there was a spike in hits on the previous version of this class. I guess the soft and chewy center fulfilled someone's need.
===== Start File BaseException.vb =====
Option Explicit On
Option Strict On
Imports System.Runtime.Serialization
Namespace Exception
' Base Exception Class. This is the base exception object from which to derive
' all application exceptions. The hierarchy should be split below this class
' into technical and business related exceptions.
()> _
Public Class BaseException
Inherits System.Exception
#Region " Class Data "
Private m_AppDomainName As String
Private m_AssemblyName As String
Private m_CurrentProcessID As String
Private m_CurrentThreadID As String
Private m_CurrentThreadUser As String
Private m_MachineName As String
Private m_UTCDateTime As System.DateTime
Private m_WindowsIdentity As String
' Collection provided to store any extra information associated
' with the exception
Private m_AdditionalInformation As New _
System.Collections.Specialized.NameValueCollection
' These provide some code simplifications below
Private DateTimeType As System.Type = System.Type.GetType("DateTime")
Private StringType As System.Type = System.Type.GetType("String")
#End Region
#Region " Constructors "
' Default constructor
Public Sub New()
MyBase.New()
m_AssemblyName = System.Reflection.Assembly.GetCallingAssembly().FullName
Me.Initialize()
Me.DoLogging("")
End Sub
' Constructor accepting a single string error message
Public Sub New(ByVal message As String)
MyBase.New(message)
m_AssemblyName = System.Reflection.Assembly.GetCallingAssembly().FullName
Me.Initialize()
Me.DoLogging("")
End Sub
' Deserialization constructor for serialized data
Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
MyBase.New(info, context)
Me.Initialize()
' Deserialize our custom properties after Initialize and before DoLogging
With info
m_UTCDateTime = .GetDateTime("seDateTime").ToUniversalTime
m_AppDomainName = .GetString("seAppDomainName")
m_AssemblyName = .GetString("seAssemblyName")
m_CurrentProcessID = .GetString("seCurrentProcessID")
m_CurrentThreadID = .GetString("seCurrentThreadID")
m_CurrentThreadUser = .GetString("seCurrentThreadUser")
m_MachineName = .GetString("seMachineName")
m_WindowsIdentity = .GetString("seWindowsIdentity")
m_AdditionalInformation = CType(.GetValue("additionalInformation", _
GetType(System.Collections.Specialized.NameValueCollection)), _
System.Collections.Specialized.NameValueCollection)
End With
Me.DoLogging("")
End Sub
' Constructor with a specified error message and a reference to the
' inner exception that is the cause of this exception
Public Sub New(ByVal message As String, ByVal inner As System.Exception)
MyBase.New(message, inner)
m_AssemblyName = System.Reflection.Assembly.GetCallingAssembly().FullName
Me.Initialize()
Me.DoLogging("")
End Sub
#End Region
#Region " Overrides "
' This override is required to add our custom properties to the serialization stream
.Security.Permissions.SecurityPermission( _
System.Security.Permissions.SecurityAction.Demand, _
SerializationFormatter:=True)> _
Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
' Serialize our custom properties
With info
.AddValue("seDateTime", m_UTCDateTime.ToLocalTime, DateTimeType)
.AddValue("seAppDomainName", m_AppDomainName, StringType)
.AddValue("seAssemblyName", m_AssemblyName, StringType)
.AddValue("seCurrentProcessID", m_CurrentProcessID, StringType)
.AddValue("seCurrentThreadID", m_CurrentThreadID, StringType)
.AddValue("seCurrentThreadUser", m_CurrentThreadUser, StringType)
.AddValue("seMachineName", m_MachineName, StringType)
.AddValue("seWindowsIdentity", m_WindowsIdentity, StringType)
.AddValue("seAdditionalInformation", _
m_AdditionalInformation, _
GetType(System.Collections.Specialized.NameValueCollection))
End With
' Call the base routine
MyBase.GetObjectData(info, context)
End Sub
' This override is required to be able to display our custom properties
Public Overrides Function ToString() As String
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder
Dim nl As String = Environment.NewLine
' Make sure we get the standard output
sb.Append(MyBase.ToString)
' Since the standard output can be nothing, we only add our variables when necessary
If (sb.Length > 0) Then
With sb
' Change this format as required
.Append(nl + nl)
.Append("Local Date Time: " + m_UTCDateTime.ToLocalTime.ToString + nl)
.Append("Windows Identity: " + m_WindowsIdentity + nl)
.Append("Machine Name: " + m_MachineName + nl)
.Append("App Domain Name: " + m_AppDomainName + nl)
.Append("Assembly Name: " + m_AssemblyName + nl)
.Append("Current Process ID: " + m_CurrentProcessID + nl)
.Append("Current Thread ID: " + m_CurrentThreadID + nl)
.Append("Current Thread User: " + m_CurrentThreadUser + nl)
' Additional info collection special handling
If (m_AdditionalInformation.Count > 0) Then
.Append("Additional Information: " + m_CurrentThreadUser + nl)
Dim s As String
For Each s In m_AdditionalInformation.AllKeys
If (m_AdditionalInformation(s).Length > 0) Then
.Append(" " + s + " = " + m_AdditionalInformation(s) + nl)
Else
.Append(" " + s + nl)
End If
Next
End If
End With
End If
Return sb.ToString
End Function
#End Region
#Region " Private Routines "
' Routine called from all Constructors to initialize our custom variables
Private Sub Initialize()
Dim errorPermissionDenied As String = "Permission Denied"
Dim errorInfoAccessException As String = "Information could not be accessed."
m_UTCDateTime = System.DateTime.UtcNow
' -----
Try
m_AppDomainName = System.AppDomain.CurrentDomain.FriendlyName
Catch e As System.Security.SecurityException
m_AppDomainName = errorPermissionDenied
Catch
m_AppDomainName = errorInfoAccessException
End Try
' -----
Try
m_CurrentProcessID = Process.GetCurrentProcess().Id.ToString
Catch e As System.Security.SecurityException
m_CurrentProcessID = errorPermissionDenied
Catch
m_CurrentProcessID = errorInfoAccessException
End Try
' -----
Try
m_AssemblyName = System.Reflection.Assembly.GetExecutingAssembly().FullName
Catch e As System.Security.SecurityException
m_AssemblyName = errorPermissionDenied
Catch
m_AssemblyName = errorInfoAccessException
End Try
' -----
Try
m_CurrentThreadID = AppDomain.GetCurrentThreadId().ToString
Catch e As System.Security.SecurityException
m_CurrentThreadID = errorPermissionDenied
Catch
m_CurrentThreadID = errorInfoAccessException
End Try
' -----
Try
m_CurrentThreadUser = System.Threading.Thread.CurrentPrincipal.Identity.Name
Catch e As System.Security.SecurityException
m_CurrentThreadUser = errorPermissionDenied
Catch
m_CurrentThreadUser = errorInfoAccessException
End Try
' -----
Try
m_MachineName = System.Environment.MachineName
Catch e As System.Security.SecurityException
m_MachineName = errorPermissionDenied
Catch
m_MachineName = errorInfoAccessException
End Try
' -----
Try
m_WindowsIdentity = System.Security.Principal.WindowsIdentity.GetCurrent().Name
Catch e As System.Security.SecurityException
m_WindowsIdentity = errorPermissionDenied
Catch
m_WindowsIdentity = errorInfoAccessException
End Try
End Sub
Private Sub DoLogging(ByVal message As String)
' Placeholder for future logging
End Sub
#End Region
#Region " Properties "
' Collection allowing unlimited additional information to be added to the exception
Public Property AdditionalInformation() As System.Collections.Specialized.NameValueCollection
Get
Return m_AdditionalInformation
End Get
Set(ByVal Value As System.Collections.Specialized.NameValueCollection)
m_AdditionalInformation = Value
End Set
End Property
' The application domain name where the exception occurred
Public ReadOnly Property AppDomainName() As String
Get
Return m_AppDomainName
End Get
End Property
' Assembly name in which the exception occurred
Public ReadOnly Property AssemblyName() As String
Get
Return m_AssemblyName
End Get
End Property
Public ReadOnly Property CurrentProcessID() As String
Get
Return m_CurrentProcessID
End Get
End Property
Public ReadOnly Property CurrentThreadID() As String
Get
Return m_CurrentThreadID
End Get
End Property
Public ReadOnly Property CurrentThreadUser() As String
Get
Return m_CurrentThreadUser
End Get
End Property
' Date and time of the exception
Public ReadOnly Property DateTime() As DateTime
Get
Return m_UTCDateTime.ToLocalTime
End Get
End Property
' Machine name where the exception occurred
Public ReadOnly Property MachineName() As String
Get
Return m_MachineName
End Get
End Property
' Windows identity under which the code was running
Public ReadOnly Property WindowsIdentityName() As String
Get
Return m_WindowsIdentity
End Get
End Property
#End Region
End Class
End Namespace
===== End File BaseException.vb =====