Tuesday, May 17, 2005

Make your .NET demos better with a TraceListener

Last night I was writing a sample application for one of my DevTeach sessions and I thought I'd share this little trick. If you've ever tried to fiqure out a speaker's sample code after the conference when you got home you'd appreciate this as well. Usually I just write copious amounts of comments in my code to help people but last night it dawned on me how I could add a very simple TraceListener to my application to easily display the application's sequence of events in a list box. This way the developer could see exactly what was going on at runtime without having to debug the code.

If you're unfamiliar with TraceListeners have a read here.

Trace listeners monitor the output of the Debug and Trace messages in an application. You can create custom listeners and then add them to the Debug or Trace Listeners collection on application startup quite easily:
Imports System.Diagnostics

Public Class Main
 ''' --------------------------------------------------------------
 ''' <summary>
 ''' Main enrty point for the application.
 ''' </summary>
 ''' <remarks>
 ''' </remarks>
 ''' <history>
 '''  [Beth] 5/16/2005 Created
 ''' </history>
 ''' --------------------------------------------------------------
 Shared Sub Main()
  '-- Registers a trace listener 
  Debug.Listeners.Add(New DemoAppTraceListener)

  Application.Run(New MainForm)
 End Sub
End Class
The idea for the DemoAppTraceListener class is very simple. I just want to globally broadcast the trace messages so that they could be displayed by the application anywhere. We can do this by declaring a Shared (static) event:
Imports System.Diagnostics

''' -----------------------------------------------------------------------------
''' Project  : DataCachingStrategies
''' Class  : DemoAppTraceListener
''' 
''' -----------------------------------------------------------------------------
''' <summary>
''' Globally broadcasts the trace messages in this demo which are displayed in the UI.
''' </summary>
''' <remarks>
''' </remarks>
''' <history>
'''  [Beth] 5/16/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Public Class DemoAppTraceListener
 Inherits TraceListener

 Public Shared Event MessageReceived(ByVal e As MessageEventArgs)

 Public Overloads Overrides Sub Write(ByVal message As String)
  RaiseEvent MessageReceived(New MessageEventArgs(message))
 End Sub

 Public Overloads Overrides Sub WriteLine(ByVal message As String)
  RaiseEvent MessageReceived(New MessageEventArgs(message))
 End Sub

End Class

''' -----------------------------------------------------------------------------
''' Project  : DataCachingStrategies
''' Class  : MessageEventArgs
''' 
''' -----------------------------------------------------------------------------
''' <summary>
''' This EventArg is used to broadcast messages from the DemoAppTraceListener.
''' </summary>
''' <remarks>
''' </remarks>
''' <history>
'''  [Beth] 5/16/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Public Class MessageEventArgs
 Inherits EventArgs

 Private m_msg As String = String.Empty
 Public ReadOnly Property Message() As String
  Get
   Return m_msg
  End Get
 End Property

 Public Sub New(ByVal msg As String)
  m_msg = msg
 End Sub
End Class
Now all you have to do is handle the event and display the messages. Oh, yea you also have to actually put Trace messages in your code now too!
Public Sub Foo
        Trace.Write("Entering Foo method")
.
.
.
End Sub
To display the messages in my demo I created an MDI parent and put a simple list box on the bottom. Then I just added a handler to the DemoAppTraceListener.MessageReceived event. I added the handler in the constructor because this code is hidden in the form designer region and people won't be distracted away from the demo code.
#Region " Windows Form Designer generated code "

Public Sub New()
 MyBase.New()

 'This call is required by the Windows Form Designer.
 InitializeComponent()

 'Add any initialization after the InitializeComponent() call
 AddHandler DemoAppTraceListener.MessageReceived, AddressOf Me.DisplayMessage
End Sub
.
.
.
Finally I added the few lines of code to display the message in the list box and select the last message:
Private Sub DisplayMessage(ByVal e As MessageEventArgs)
 Me.lstMsgs.Items.Add(Me.lstMsgs.Items.Count.ToString + ". " + e.Message)
 Me.lstMsgs.SelectedIndex = Me.lstMsgs.Items.Count - 1
End Sub
Another cool thing about using a TraceListener is that the developer can still copy/paste the code in the demo into their own applications without having to remove the Trace messages if they don't want to.

Now if you come to my data caching session at DevTeach you'll get to see the TraceListener in action! ;-)

3 comments:

Eric Therrien said...

You could also use the log4net logging mechanisme (from the folks at Apache) wich you can easilly toggle on or off globally or for some part of your application and do plenty of stuff like re-routing to debug window, file, event viewer, etc all with this package...

Beth Massi said...

Cool. Thanks for the info, I'll check it out.

Anonymous said...

.NET 2 already has an event model on the TraceContext to do just this.

http://quickstart.developerfusion.co.uk/QuickStart/aspnet/doc/monitoring/messages.aspx