Recipe7.19.Looking at Exceptions in a New Way Using Visualizers


Recipe 7.19. Looking at Exceptions in a New Way Using Visualizers

Problem

You want to see all of the exception data laid out differently from Visual Studio's presentation, as your exceptions usually have multiple inner exceptions showing the root cause.

Solution

Create a debugger visualizer by deriving a class from Microsoft.VisualStudio. DebuggerVisualizers.DialogDebuggerVisualizer that can be plugged in to Visual Studio for all exception types. The easiest way to create one of these is to create a class library project and then add a class that derives from DialogDebuggerVisualizer. You will create the ExceptionVisualizer to show exceptions with a focus on getting right to the inner exception information. The ExceptionDisplay class is implemented as the presentation for the ExceptionVisualizer.

First, add a reference to the Microsoft.VisualStudio.DebuggerVisualizers namespace in the class library project that was created. Now add the using directive like this:

 using Microsoft.VisualStudio.DebuggerVisualizers; 

Next declare the ExceptionVisualizer class. The only method you must implement is Show, which passes in the IDialogVisualizerService and IVisualizerObjectProvider interfaces. To get the exception object that the visualizer is being asked to display, call the GetObject method on the IVisualizerObjectProvider interface and cast it to System.Exception. This exception is assigned to a WinForm called ExceptionDisplay by setting the CurrentException property. ExceptionDisplay is the display piece of the visualizer and will be described in detail shortly. The second method provided here is the TestShowVisualizer method. This is simply a test method to make it easier to develop the visualizer. It is rather challenging to develop the visualizer while running inside of Visual Studio (and the target program); by implementing this test method, you can create a simple test application to feed the exception directly to the visualizer through the use of the VisualizerDevelopmentHost. The VisualizerDevelopmentHost class allows you to "sandbox" your visualizer outside of Visual Studio while still providing it with the interfaces it expects from Visual Studio for data that can be provided to the host. TestShowVisualizer takes in an exception object and sets up the host, then calls ShowVisualizer to invoke the visualizer code with the given exception object:

 namespace ExceptionalVisualizer {     public class ExceptionVisualizer : DialogDebuggerVisualizer     {         override protected void Show(IDialogVisualizerService windowService,                 IVisualizerObjectProvider objectProvider)         {             ExceptionDisplay display = new ExceptionDisplay();             display.CurrentException = (Exception)objectProvider.GetObject();             display.ShowDialog();         }         public static void TestShowVisualizer(object exception)         {           VisualizerDevelopmentHost visualizerHost =             new VisualizerDevelopmentHost(exception,              typeof(ExceptionVisualizer));           visualizerHost.ShowVisualizer();         }     } } 

ExceptionDisplay is a Windows Form that allows for displaying an exception in a more tabular format that shows very quickly the inner exception information, as shown in Figure 7-6. The code for the ExceptionDisplay is found in the sample code in the ExceptionDisplay.cs file.

Figure 7-6. ExceptionDisplay window showing exception details


The context menu provides access to the Message Box shown in Figure 7-7 when you right-click on one of the exception types. It allows you to see the call stack for the exception.

Figure 7-7. Call stack display information from ExceptionDisplay


Or the exception data can be copied to the clipboard, as shown in Figure 7-8.

Figure 7-8. Result from copying exception data to the clipboard


The clipboard data appears in XML format, as shown in Example 7-7.

Example 7-7. Exception data in XML format

 <ApplicationException_0>   <Message>Problems calling level 1</Message>   <Source>TestVisualizer</Source>   <StackTrace> at TestVisualizer.MainClass.StartExceptionChain() in C:\PRJ32\Book_2_0\ TestCode\TestVisualizer\Main.cs:line 30    at TestVisualizer.MainClass.Main(String[] args) in C:\PRJ32\Book_2_0\TestCode\ TestVisualizer\Main.cs:line 13</StackTrace>   <HelpLink />   <TargetSite />   <Data />   <InnerExceptions>     <InvalidOperationException_1>       <InnerMessage>Problems calling level 2</InnerMessage>       <InnerSource>TestVisualizer</InnerSource>       <InnerStackTrace> at TestVisualizer.MainClass.CallLevel1() in C:\PRJ32\Book_2_0\ TestCode\TestVisualizer\Main.cs:line 42    at TestVisualizer.MainClass.StartExceptionChain() in C:\PRJ32\Book_2_0\TestCode\ TestVisualizer\Main.cs:line 26</InnerStackTrace>       <InnerHelpLink />       <InnerTargetSite />       <InnerData />       <NotSupportedException_2>         <InnerMessage>Level 2 calls are not supported</InnerMessage>         <InnerSource>TestVisualizer</InnerSource>         <InnerStackTrace> at TestVisualizer.MainClass.CallLevel2() in C:\PRJ32\Book_2_ 0\TestCode\TestVisualizer\Main.cs:line 48    at TestVisualizer.MainClass.CallLevel1() in C:\PRJ32\Book_2_0\TestCode\TestVisualizer\ Main.cs:line 38</InnerStackTrace>         <InnerHelpLink />         <InnerTargetSite />         <InnerData />       </NotSupportedException_2>     </InvalidOperationException_1>   </InnerExceptions> </ApplicationException_0> 

The other important part of setting up the visualizer infrastructure is that the class library needs to display the System.Diagnostics.DebuggerVisualizerAttribute for each object type it is going to support. To support System.Exception, the following attribute is necessary:

 [assembly: System.Diagnostics.DebuggerVisualizer( typeof(ExceptionalVisualizer.ExceptionVisualizer), typeof(VisualizerObjectSource), Target = typeof(System.Exception), Description = "Exception Visualizer")] 

The DebuggerVisualizerAttribute can have the properties listed in Table 7-3.

Table 7-3. DebuggerVisualizerAttribute properties

Property

Description

Description

Description of the visualizer

Target

The target type for the visualizer to be available for when the attribute is applied at the assembly level

TargetTypeName

Specifies the fully qualified type name when the attribute is applied at the assembly level

VisualizerObjectSourceTypeName

The fully qualified type name of the visualizer object

VisualizerTypeName

The fully qualified name of the visualizer type


Once the Visualizer is built, it needs to be deployed to one or both of the following locations:

  • Visual Studio Install Directory>\Common7\Packages\Debugger\Visualizers (for all users)

  • My Documents>\Visual Studio\Visualizers (for the current user)

This is where Visual Studio 2005 looks for the visualizers on debugging startup so that they can be loaded into the processes. The visualizer is accessed through a magnifying glass icon when the mouse hovers over an exception object in the debugger, as shown in Figure 7-9. Select the down arrow and choose the type of visualizer to use. These type names are taken from the Description property of the DebuggerVisualizerAttribute.

Figure 7-9. DebuggerVisualizer selection window in Visual Studio


Discussion

There is one big gotcha when building a debugger visualizer. If you pick a common base type (like System.Exception) and register the visualizer for that base type, you get only the base type occurrences. For System.Exception this means that the ExceptionVisualizer would be brought up only in the visualizer list for System.Exception exceptions. This being less than ideal for a more general purpose viewer such as we're creating here, there is a bit more automation code to help create the attributes to place on the ExceptionVisualizer.

Since the DebuggerVisualizerAttribute is at assembly scope, it is possible to have a separate C# file (*.cs) that holds nothing but the attributes for the exception types that the ExceptionVisualizer should support. To create this file with all of the exception types in the framework, the code enumerates the assemblies in the current framework directory and creates a DebuggerVisualizerAttribute for each type with System.Exception as the base class. To do this, a bit of code from Recipes 13.8, 20.4, and 20.6 is borrowed to get the exception types as shown in GetExceptionTypes in Example 7-8, which returns a List<Type>.

Example 7-8. GetExceptionTypes class

 public static List<Type> GetExceptionTypes() {     // Init our list     List<Type> exceptionTypes = new List<Type>(100);     List<string> typeFullNames = new List<string>(100);     // Get the System.Exception type to reflect on.     Type type = Type.GetType("System.Exception");     // Get the framework directory (20.5).     string frameworkDir = GetCurrentFrameworkPath();     string[] asmFiles = Directory.GetFiles(frameworkDir, "*.dll",                         SearchOption.AllDirectories);     foreach (string asmFile in asmFiles)     {         // Check the current module for subclasses of System.Exception.         List<Type> subClasses = null;         try         {             // Get the subclasses in this assembly for System.Exception             // from Recipe 13.8.             subClasses = GetSubClasses(asmFile, type);         }         catch (FileLoadException)         {             // Might not be a .NET assembly so skip it             continue;         }         // Write out the subclasses for this type.         if (subClasses.Count > 0)         {             // Store the new types from this assembly.             foreach (Type t in subClasses)             {                 if (!exceptionTypes.Contains(t))                 {                     // Skip crt exceptions.                     if ((t.FullName.IndexOf("<CrtImplementationDetails>") == -1)&&                         (t.FullName.IndexOf("com.") == -1))                     {                         // Some types have different AQNs but same FullName.                         // Filter them out.                         if (!typeFullNames.Contains(t.FullName))                         {                             // No nested exception classes as they are                             // usually private                             if (!t.FullName.Contains("+"))                             {                                 typeFullNames.Add(t.FullName);                                                                  // Just work on public exceptions.                                 if ((t.Attributes & TypeAttributes.Public) != 0)                                 exceptionTypes.Add(t);                             }                         }                     }                 }             }         }     }     return exceptionTypes; } 

Once you have the list of exceptions, then you can crank out the attributes in the C# file by rolling over the types and calling the WriteDebuggerVisualizerAttribute method for each exception we want the ExceptionVisualizer to support:

 static void Main(string[] args) {     try     {         // Get all of the exception types.         List<Type> exceptionTypes = GetExceptionTypes();         List<Assembly> assembliesReferenced = new List<Assembly>(100);         FileStream fs =         new FileStream(@"..\..\..\ExceptionSupport.cs",                 FileMode.OpenOrCreate, FileAccess.Write);         StreamWriter writer = new StreamWriter(fs);         // Add the initial using statement.         writer.WriteLine("// Generated on " + DateTime.Now.ToString("F") +                 " by ExceptionSupportCreator");         writer.WriteLine("using Microsoft.VisualStudio.DebuggerVisualizers;");         writer.WriteLine("");         foreach (Type t in exceptionTypes)         {             if (!ignoreAssemblies.Contains(t.Assembly.FullName))             {                 if (!assembliesReferenced.Contains(t.Assembly))                 {                     writer.WriteLine("//Adding for assembly " +                         t.Assembly.FullName);                     assembliesReferenced.Add(t.Assembly);                 }                 WriteDebuggerVisualizerAttribute(t, writer);             }         }         writer.WriteLine("");         writer.WriteLine("");         writer.WriteLine("// Add references for these assemblies");         foreach (Assembly assm in assembliesReferenced)         {             writer.WriteLine("// {0}", assm.FullName);         }         writer.Flush();         writer.Close();     }     catch (Exception ex)     {         Console.WriteLine(ex.ToString());     } } 

The WriteDebuggerVisualizerAttribute method is shown here:

 public static void WriteDebuggerVisualizerAttribute(Type t, StreamWriter writer) {     // Write out the debugger visualizer attrib for this exception type     writer.WriteLine("");     writer.WriteLine("[assembly: System.Diagnostics.DebuggerVisualizer(");     writer.WriteLine("typeof(ExceptionalVisualizer.ExceptionVisualizer),");     writer.WriteLine("typeof(VisualizerObjectSource),");     writer.WriteLine("Target = typeof(" + t.FullName + "),");     writer.WriteLine("Description = \"Exception Visualizer\")]");     writer.WriteLine(""); } 

Once the ExceptionSupport.cs file is created, it is built into the class library containing the visualizer, and then the visualizer will support all of the various exception types. In order for this to compile correctly, the references to the framework assemblies with the exceptions must be added.

See Also

See Recipes 13.8, 20.4, and 20.6; see the "DebuggerVisualizerAttribute" topic in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net