Recipe 7.19. Looking at Exceptions in a New Way Using VisualizersProblemYou 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. SolutionCreate 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 detailsThe 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 ExceptionDisplayOr 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 clipboardThe clipboard data appears in XML format, as shown in Example 7-7. Example 7-7. Exception data in XML format
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.
Once the Visualizer is built, it needs to be deployed to one or both of the following locations:
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 StudioDiscussionThere 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
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 AlsoSee Recipes 13.8, 20.4, and 20.6; see the "DebuggerVisualizerAttribute" topic in the MSDN documentation. |