Geeks With Blogs

News
Neat Stuff Read all my hurricane entries While you are here, visit the Geeks With Blogs main feed
Advertising
Links Status of the Navy
Channel 9

.NET Hobbyist Programmer Staying Confused in a Busy World

Update 1: I have posted a new version of the code given below.  The comments in this post still apply, but the code has been revised and expanded.

Update 2: A C# version of this base exception class is posted here.


Now it is time to get serious with some code.  This is the first element of my exception handling --- the custom base exception class.  Here is an updated list of blog entries which address exceptions to standard design.  I will refer to some of these further on below.

  1. Brad Abrams, Exceptions and Error Codes
  2. Brad Abrams, Small Design Guideline Update: Overriding Exception.ToString
  3. Brad Abrams, Design Guidelines Update: Exception Message Guidelines
  4. Brad Abrams, Introducing the .NET Framework Standard Library Annotated Reference Vol 1
  5. Eric Gunnerson, Exception class, return codes, and messages
  6. Chris Brumme, The Exception Model
  7. Chris Brumme, Unhandled exceptions

The Microsoft documentation I referenced earlier describes how you should create a base class for your exception hierarchy.  Here is the Microsoft description of this base class.  "Your application should have a single base exception class that derives from ApplicationException. This class serves as the base class for all of your application's exception classes.  You should add fields to this base class to capture specific information such as the date and time the exception occurred, the machine name, and so on. This encapsulates the common exception details into the single base class and makes this information available to your exception classes through inheritance."  Contrary to this advice, I inherit from System.Exception rather than System.ApplicationException based on the discussion in blog entry #4 above.

Following the last quote is a C# example that I used as the initial basis for this class once it was converted to VB.  This is not a perfect exception base class but it is a start in that it includes the four necessary constructors.

    Public Sub New()
    Public Sub New(ByVal message As String)
    Public Sub New(ByVal message As String, ByVal inner As System.Exception)
    Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

This will give you a simple base class with the minimum code required for proper function.  The first three constructors are for normal use in programs, while the fourth is for deserialization.  The sample code also includes a single example of adding a custom property to the exception.  Besides the property code, you also need to handle both directions of the serialization.  Microsoft puts it this way: "If your exceptions add fields to the base ApplicationException class, you will need to persist these values programmatically into the serialized data stream.  This can be done by overriding the GetObjectData method and adding these values to the SerializationInfo object."  I could not have said it better.  You can see this in the code below.

"The values can be retrieved from the SerializationInfo object in the constructor of your exception object using info.GetValue or one of the Get methods of the SerializationInfo object."  My example uses the GetString, GetInteger, and GetDate methods.  "Your custom exceptions should be able to maintain their state as they are marshaled across local and remote boundaries.  Even if your application does not currently span remoting boundaries, providing serialization support ensures that exception information will not be lost if your application is later modified to do so.  If you do not programmatically serialize your exception's custom values, they will not be set properly when they are deserialized at the client.  Providing serialization support also allows your application to serialize all the information about your exception and store it for logging purposes."

The sample code shows adding the Machine Name.  Microsoft recommends including more data, but does not offer you the code.  My sample adds the following to System.Exception:

  • Date and time of exception (System.DateTime.UtcNow)
  • Machine name (System.Environment.MachineName)
  • Application Domain Name (System.AppDomain.CurrentDomain.FriendlyName)
  • Assembly Name (System.Reflection.Assembly.GetExecutingAssembly().FullName)
  • Current Process ID (Process.GetCurrentProcess().Id)
  • Thread ID (AppDomain.GetCurrentThreadId())
  • Thread User (System.Threading.Thread.CurrentPrincipal.Identity.Name)

The DateTime is stored internally as a UTC value and converted for external use and serialization per the best practices recommended in this MSDN article.

Blog entry #2 reminded me that I also had to override System.Exception.ToString in order to add my custom properties.  That was easy, but I had to allow for a no output scenario should the exception be propagated without any information in it.

For the moment, there is just a stub routine for logging.  This will get filled in once I have my logging framework integrated.

Now that the preliminaries are done, here is the code for my exception base class.

===== Start File BaseException.vb =====

Option Explicit On
Option Strict On

Imports System.Runtime.Serialization

Namespace Exception

  _
  Public Class BaseException
    Inherits System.Exception

#Region " Class Data "

    Private m_AppDomainName As String
    Private m_AssemblyName As String
    Private m_CurrentProcessID As Integer
    Private m_CurrentThreadID As Integer
    Private m_CurrentThreadUser As String
    Private m_MachineName As String
    Private m_UTCDateTime As System.DateTime
    ' These provide some code simplifications below
    Private DateTimeType As System.Type = System.Type.GetType("DateTime")
    Private IntegerType As System.Type = System.Type.GetType("Integer")
    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()
      m_AssemblyName = System.Reflection.Assembly.GetCallingAssembly().FullName
      ' Deserialize our custom properties after Initialize and before DoLogging
      Me.DateTime = info.GetDateTime("seDateTime")
      m_AppDomainName = info.GetString("seAppDomainName")
      m_AssemblyName = info.GetString("seAssemblyName")
      m_CurrentProcessID = info.GetInt32("seCurrentProcessID")
      m_CurrentThreadID = info.GetInt32("seCurrentThreadID")
      m_CurrentThreadUser = info.GetString("seCurrentThreadUser")
      m_MachineName = info.GetString("seMachineName")
      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 "

    Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
      ' Serialize our custom properties
      With info
        ' This value must go through the property for conversion to Local Time for proper serialization
        .AddValue("seDateTime", Me.DateTime, DateTimeType)
        ' The rest of the values may be serialized from local variables
        .AddValue("seAppDomainName", m_AppDomainName, StringType)
        .AddValue("seAssemblyName", m_AssemblyName, StringType)
        .AddValue("seCurrentProcessID", m_CurrentProcessID, IntegerType)
        .AddValue("seCurrentThreadID", m_CurrentThreadID, IntegerType)
        .AddValue("seCurrentThreadUser", m_CurrentThreadUser, StringType)
        .AddValue("seMachineName", m_MachineName, StringType)
      End With
      ' Call the base routine
      MyBase.GetObjectData(info, context)
    End Sub

    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("Machine Name: " + m_MachineName + nl)
          .Append("App Domain Name: " + m_AppDomainName + nl)
          .Append("Assembly Name: " + m_AssemblyName + nl)
          .Append("Current Process ID: " + m_CurrentProcessID.ToString + nl)
          .Append("Current Thread ID: " + m_CurrentThreadID.ToString + nl)
          .Append("Current Thread User: " + m_CurrentThreadUser + nl)
        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()
      m_AppDomainName = System.AppDomain.CurrentDomain.FriendlyName
      m_CurrentProcessID = Process.GetCurrentProcess().Id
      m_AssemblyName = System.Reflection.Assembly.GetExecutingAssembly().FullName
      m_CurrentThreadID = AppDomain.GetCurrentThreadId()
      m_CurrentThreadUser = System.Threading.Thread.CurrentPrincipal.Identity.Name
      m_MachineName = System.Environment.MachineName
      m_UTCDateTime = System.DateTime.UtcNow
    End Sub

    Private Sub DoLogging()
      ' Placeholder for future logging
    End Sub

#End Region

#Region " Properties "

    Public Property AppDomainName() As String
      Get
        Return m_AppDomainName
      End Get
      Set(ByVal Value As String)
        m_AppDomainName = Value
      End Set
    End Property

    Public Property AssemblyName() As String
      Get
        Return m_AssemblyName
      End Get
      Set(ByVal Value As String)
        m_AssemblyName = Value
      End Set
    End Property

    Public Property CurrentProcessID() As Integer
      Get
        Return m_CurrentProcessID
      End Get
      Set(ByVal Value As Integer)
        m_CurrentProcessID = Value
      End Set
    End Property

    Public Property CurrentThreadID() As Integer
      Get
        Return m_CurrentThreadID
      End Get
      Set(ByVal Value As Integer)
        m_CurrentThreadID = Value
      End Set
    End Property

    Public Property CurrentThreadUser() As String
      Get
        Return m_CurrentThreadUser
      End Get
      Set(ByVal Value As String)
        m_CurrentThreadUser = Value
      End Set
    End Property

    Public Property DateTime() As DateTime
      Get
        Return m_UTCDateTime.ToLocalTime
      End Get
      Set(ByVal Value As DateTime)
        m_UTCDateTime = Value.ToUniversalTime
      End Set
    End Property

    Public Property MachineName() As String
      Get
        Return m_MachineName
      End Get
      Set(ByVal Value As String)
        m_MachineName = Value
      End Set
    End Property

#End Region

  End Class

End Namespace

===== End File BaseException.vb =====

Posted on Friday, March 26, 2004 8:10 PM Programming | Back to top


Comments on this post: Custom Base Exception Class - V1 (VB)

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Mark Treadwell | Powered by: GeeksWithBlogs.net