Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Visual Studio 2005 Visualizers


August, 2005: Visual Studio 2005 Visualizers

James is a .NET architect and author of Visual Studio Hacks (O'Reilly & Associates, 2004). He can be contacted at javery@ infozerk.com.


Debuggers have long been a part of the development process. Ever since the first debugger, programmers have been using debuggers as a way to step through executing code, view the values of variables, and watch how the program reacts to different situations. However, most modern debuggers have failed to adapt to the increased prevalence of complex objects in modern programming languages. These debuggers simply display the object in a tree structure that can be hard to navigate and hard to understand, or simply summarize the object using its string representation in a tool tip. This becomes even more problematic when dealing with objects that store their data as XML or some type of encoded string. Trying to determine the value of these types of variables inside the debugger becomes almost impossible.

One notorious example in .NET is the DataSet object. Trying to get a look at the data inside of a DataSet is often painful as you can have multiple tables and then have a plethora of rows in each of those tables. Trying to find an individual row is an exercise in browsing through numerous tables and possibly hundreds of rows. Figure 1 shows a watch window in Visual Studio.NET 2003 displaying the data inside of a single row in a DataSet.

To solve this problem, Visual Studio 2005 introduces "debugger visualizers"—Windows Forms dialogs that can be used to create graphical views into the value of an object, and in some cases even add additional troubleshooting functionality to the debugger. While debugging, you see a small magnifying glass next to certain types. When you click on this magnifying glass, you see one or more visualizers listed in the dropdown menu to choose from, as in Figure 2.

After selecting the visualizer to use, Visual Studio then displays that visualizer; the dataset visualization can be seen in Figure 3. Using the DataSet visualizer, you can easily navigate through the tables and rows inside of a DataSet and edit the data of the DataSet. It will then be persisted back to your application. The new visualizer is leaps and bounds ahead of the old tree view model of digging through rows and tables, with an excellent editing ability to boot. Microsoft is planning to release HTML, XML, and string visualizers in addition to the DataSet visualizer with the final release of Visual Studio 2005. (This article and its code are based on the Beta 2 release of Visual Studio 2005. There is a possibility that some of the details will change before the final release of Visual Studio 2005.)

How It Works

Visualizers run in both the application you are debugging and the debugger: This means that they must send the object you want to visualize between these two different application domains. Visualizers accomplish this by using a System.IO.Stream, meaning that any data that will be passed from the application to the visualizer must be serialized and sent through this stream. Thus, writing a visualizer for an object that is not serializable requires a little bit of extra work.

The visualizer infrastructure also provides an outgoing stream to pass data back from the visualizer to the executing application, letting you create visualizers that can edit the data they are visualizing. This means that visualizers can be more than just tools used to inspect the value of an object; they can actually become editors or analyzers, as you will see in a later example.

Once the data has been transferred from the executing application to the visualizer, there are virtually endless possibilities of what can be done because the dialog shown to users is simply a Windows form shown in dialog mode. Just about anything that can be done in a normal Windows Forms application could be done from a visualizer.

Visualizer Extensibility Model

While the default visualizers are an excellent new feature, the best part is that the model is completely extensible. Without too much effort, you can quickly develop a custom visualizer, either for objects in the .NET Framework or for objects in your own projects.

The visualizer extensibility model allows for visualizers to be linked to multiple classes, and multiple visualizers to be linked to a single class. To illustrate, I present a simple visualizer that can be used when debugging base64 encoded strings. Normally when strings are encoded in base64, their value is difficult to view in the debugger. You would have to cut-and-paste the value to some other tool and convert the string. Updating the string would be equally hard. Both of these problems can be solved by writing a visualizer to view, edit, and save base64 strings. The goal of this visualizer is to let users view the decoded string, then edit the value and save it back to the executing application.

The first step for creating a visualizer is to create a new Class Library project in Visual Studio 2005. The next step is to right click on the references folder and add a new reference to the Microsoft.VisualStudio.DebuggerVisualizers assembly, which you can find in the .NET tab of the Add References dialog. This is the assembly that contains the interface and abstract class needed to write a custom visualizer.

A basic visualizer consists of a number of different parts:

  • The object source that Visual Studio uses to transfer data from the application to the visualizer and back.
  • The Windows form that displays the data.
  • A small debugger side class that ties these two components together.

The Object Source

The role of the object source is to facilitate the transfer of the object from the application being debugged through the Visual Studio infrastructure and to the visualizer form. If the object you are visualizing is serializable, then the default object source will actually do the serialization for you. If the object is not serializable, then you will need to determine what values can be passed to the visualizer and send that data; for instance, if visualizing a SqlConnection object, you might send the connection string and connection status because the entire object cannot be serialized and sent.

Because the string object is serializable, I actually don't need to create a custom object provider. However, I do so anyway for the sake of completeness and to illustrate what needs to be done in situations where automatic serialization is not possible.

To create the object source:

  1. Add a new class to your project.
  2. Add a using statement for the Microsoft.VisualStudio.DebuggerVisualizers namespace to your class.
  3. Set your class to inherit from VisualizerObjectSource.
  4. Override the GetData method and provide the transport method for your object; in this example, it is simply a matter of serializing the object using the binary formatter.

Listing One presents the complete object source.

The Form

The form is where users view and interact with the object they are visualizing. Before you can add a Windows form to your Class Library project, you first need to right click on the references folder and add a reference to the Windows.Forms assembly.

For this visualizer, the form needs to:

  1. Accept the object source as part of its constructor; this allows the form to read/write the value of the variable through the object source.
  2. Convert the string from base64 encoding to plain text.
  3. Convert the plain text back to base64 and save the object.

The saving of the object is done through the object provider using the ReplaceObject() method (this method can be overridden as well if needed). Listing Two is the code used for the form.

The Debugger Side Class

The debugger side class is the last class needed for the visualizer. This class is used by Visual Studio to actually display your custom form. To create the debugger side class, you simply add another class to your project and set it to inherit from the DialogDebuggerVisualizer class. You then need to override the Show method and inside of that method create an instance of your visualizer form passing in the object source, then lastly, call the windowService.ShowDialog() method passing in your form. When the user clicks on your visualizer, the debugger calls your Show method, thus creating an instance of your form and displaying it to your user.

Listing Three presents the DebuggerVisualizer class.

Deploying Visualizers

Before the visualizer is ready to be deployed, you must first add an assembly-level attribute such as this:

[assembly:
DebuggerVisualizer(typeof(Base64String-
Visualizer.Base64StringVisualizer),
typeof(Base64StringVisualizer.Base64-
VisualizerObjectSource),
Target = typeof(string), Description
= "Base64 StringVisualizer")]

The first parameter specifies your debugger side class that inherits from DialogDebuggerVisualizer. The second value specifies your custom object source. The Target parameter specifies the type that this visualizer is built for, and the description parameter specifies the name for the visualizer that should be displayed in the visualizers menu.

An assembly can have multiple DebuggerVisualizer attributes, so you can link a visualizer to multiple target types or include multiple visualizers in a single assembly.

Once you have added the assembly level attribute, you install the assembly. Adding a new visualizer does not involve registry settings or even configuration settings, you drop your assembly into one of two different files paths:

  • \Documents and Setting\%profile%\My Documents\Visual Studio\Visualizers. Visualizers added here will only be available for this user. (You may need to create this directory if it does not exist.)
  • \%vs install directory%\Common7\ Packages\Debugger\Visualizers. Visualizers added here will be available to every user on this machine.

Debugging Visualizers

Because the visualizer is an assembly with no entry point, it is inherently hard to directly debug. Microsoft anticipated this and made it easy to debug visualizers by including the special class VisualizerDevelopmentHost. This class can be used to directly load your visualizer without the hassle of loading and debugging another application. To debug your debug visualizer:

  1. Add a new Console Application Project to your solution and set it as the default startup project.
  2. Right click on the references folder in your console application and add references to your visualizer project as well as the Microsoft.VisualStudio.DebuggerVisualizers namespace.
  3. Declare an instance of the variable you want to visualize. In this example, I create a base64 encoded string.
  4. Pass your local variable to the VisualizerDevelopmentHost as in Listing Four.

You can now place breakpoints in your visualizer and then simply launch the debugger, and the VisualizerDevelopmentHost launches your visualizer just like it was being launched from a normal running instance of Visual Studio.

Running the Visualizer

To run the visualizer, you simply need to create a test project, create a base64 encoded string, and then launch the debugger. You first see the visualizer in the dropdown visualizer menu, as in Figure 4. After selecting the visualizer in the menu, you will see the running visualizer. You can view the base64 string, the decoded string, and then edit and save the decoded string value. When you save the value, it is sent back to the executing application.

Visualizers make debugging complex, modern day applications easier, and will truly enhance the debugging experience in Visual Studio 2005. The extensibility model makes it easy to create your own visualizers for either framework classes or your own classes.

DDJ



Listing One

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System.Runtime.Serialization.Formatters.Binary;

namespace Base64StringVisualizer
{
    class Base64VisualizerObjectSource : VisualizerObjectSource
    {
     public override void GetData(object target,System.IO.Stream outgoingData)
     {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(outgoingData, target);
     }
  }
}
Back to article


Listing Two
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System.Runtime.Serialization.Formatters.Binary;

namespace Base64StringVisualizer
{
   public partial class Base64StringVisualizerForm : Form
   {
     private IVisualizerObjectProvider _objectSource;
     public Base64StringVisualizerForm(IVisualizerObjectProvider objectSource)
     {
         _objectSource = objectSource;
         InitializeComponent();
     }
     private void Form1_Load(object sender, EventArgs e)
     {
        PopulateForm();
     }
     private void PopulateForm()
     {
        BinaryFormatter formatter = new BinaryFormatter();
        string base64string = 
                 (string) formatter.Deserialize(_objectSource.GetData());
        tbBase64.Text = base64string;
            tbPlainText.Text = base64Decode(base64string);
     }
     private string base64Decode(string base64string)
     {
         try
         {
             UnicodeEncoding encoder = new UnicodeEncoding();
             string textString = 
                   encoder.GetString(Convert.FromBase64String(base64string));
             return textString;
         }
         catch (Exception e)
         {
             throw new Exception("Error during decoding", e);
         }
     }
     private string base64Encode(string textString)
     {
         try
         {
            string base64string = 
                Convert.ToBase64String(ConvertStringToByteArray(textString));
            return base64string;
         }
         catch (Exception e)
         {
             throw new Exception("Error during encoding", e);
         }
    }
    public static byte[] ConvertStringToByteArray(string stringToConvert)
    {
         return (new UnicodeEncoding()).GetBytes(stringToConvert);
    }
    private void btnCancel_Click(object sender, EventArgs e)
    {
        this.Close();
    }
    private void btnOK_Click(object sender, EventArgs e)
    {
         string base64string = base64Encode(tbPlainText.Text);
         _objectSource.ReplaceObject(base64string);
         this.Close();
    }   
  }
}
Back to article


Listing Three
using Microsoft.VisualStudio.DebuggerVisualizers;
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

[assembly: DebuggerVisualizer(typeof(Base64StringVisualizer.DebuggerSide),
   typeof(Base64StringVisualizer.Base64VisualizerObjectSource),
   Target = typeof(string), Description = "Base64 String Visualizer")]

namespace Base64StringVisualizer
{
	class DebuggerSide : DialogDebuggerVisualizer
	{
		protected override void Show(IDialogVisualizerService windowService, 
		                             IVisualizerObjectProvider objectProvider)
		{
			Base64StringVisualizerForm form = 
			   new Base64StringVisualizerForm(objectProvider);
			windowService.ShowDialog(form);
		}
	}
}
Back to article


Listing Four
string base64string = "This is a test of the Base64 system";
base64string = base64Encode(s); //using method from Listing 2

VisualizerDevelopmentHost visualizerHost = 
 new VisualizerDevelopmentHost(base64string, 
                               typeof(Base64StringVisualizer));
visualizerHost.ShowVisualizer();
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.