Posts Tagged With DebuggerVisualizer - Musing, Rants & Jumbled Thoughts

Header Photo Credit: Lorenzo Cafaro (Creative Commons Zero License)

Loading Visualizers (and Where To Install)

Visualizers registered in assemblies currently loaded into the application will be available. That is, the DebuggerVisualizer attribute must have been loaded and the assembly housing the visualizer must be loaded (otherwise you'll get an error message from Visual Studio saying it couldn't find the assembly).

If the visualizers are included in projects in the currently loaded solution, typically everything "just works". However, if the visualizers are in a seperate assembly (not part of the current solution) which you reference, you may need to force the assembly to be loaded. If you use the actual type in the DebuggerVisualizer attribute (ex: [DebuggerVisualizer(typeof(SomeTypeVisualizer))]), that should be enough. But if you use the string-based constructor for the attribute (ex: [DebuggerVisualizer("MyVisualizers.SomeTypeVisualizer")]), you may need to force the assembly to be loaded. One way to do that is add a static constructor to your main class that references a type from the visualizer assembly, like this:

class Program
{
    static Program()
    {
        if (Debugger.IsAttached)
        {
            //force loading of the visualizer assembly and Microsoft.VisualStudio.DebuggerVisualizers
            var foo = typeof(VisualizerExamples.DebuggerVisualizer.Exception.SimpleExceptionVisualizer);
        }
    }

This, however, makes it pretty difficult to include standalone visualizers in NuGet packages. (Though, including visualizers in the assembly with the types they visualize would work via NuGet)

The more common approach is to install the assembly with your visualizers into one of two places:

  • <VisualStudioInstallPath>\Common7\Packages\Debugger\Visualizers\ to make them available to all users on the computer.
  • My Documents\<VisualStudioVersion>\Visualizers\ to make them available to the current user.

Note: The visualizers must be installed on the machine running the application being debugged, so if you're using a remote debugging session, they must be installed on the remote machine as well as your local machine.

You can find a number of visualizers in the Visual Studio Marketplace, and they'll install into one of these locations.

Security Considerations

Keep in mind that parts of the Debugger Visualizer effectively run with the security level of Visual Studio, and all too many of us developers run Visual Studio as Administrator (thanks, in large part, to the crazy that is IIS's security model). So that means the visualizer you select could happily go off and delete your entire hard drive, send all of your source code to an FTP site, or whatever Hollywood-style hacker-in-a-hoodie nefarious stuff you can imagine.

So make sure you know and trust the source for any visualizers you choose to run. And since visualizers can been shipped with libraries you're using, they may be available without you having directly installed anything.

Visual Studio already has some safeguards in place, such as disallowing app-side VisualizerObjectSource object running in partial-trust. But, in my experience, very few of us make use of anything but full-trust environments. So Don't Be Stupid and pay a little extra attention to which visualizers you see vs. what you expect to see listed.

Supported Types and Limitations

Generics have very poor support. You can only register a visualizer with an open generic type (ex: Dictionary<,>) but not a constructed type (ex: Dictionary<int, string>). But in the visualizer, you only get an object, so without a lot of crazy reflection, you have no way of knowing the types used in the generic placeholders.

Universal Windows Platform (UWP) apps and Windows "Store" apps do not support custom visualizers, though they can use the built-in string visualizers (Text, HTML, XML, JSON).

The visualizers described in this post only work for managed code. Native VisualCpp assemblies won't use them.

Visual Studio for Mac uses the Mono Soft-Mode debugger, which does not use the same visualizers as Visual Studio for Windows.

Make sure to take a look at the other posts in my Debugger Visualizer series.



This post builds upon the first post in the series Writing A (ReadOnly) Custom Debugger Visualizer for Visual Studio. Please at least skim through that post to understand the topics in this one.

Making Your Visualizer Editable

There are cases where just viewing data from the object you're visualizing during a debugging session isn't enough. You'd like to modify the object in some way. Perhaps to setup a specific failure/success scenario or replicate a bug/bug fix.

If you're using a read-only visualizer, like what we've built so far in this series, you'd need to close the visualizer and use the Immediate Window to modify the data. Particularly if the value you want to modify is deeply nested in your object graph, or you have a bunch of items in a child collection and finding the right one to modify is tedious, then using the Immediate Window can be an exercise in frustration.

Luckily, the Debugger Visualizer API allows a great amount of flexibility to modify the object we're visualizing.

The Components Involved

Going from a read-only visualizer to one that can modify/interact with the underlying object means taking advantage of additional methods in the IVisualizerObjectProvider and VisualizerObjectSource.

Specially, in IVisualizerObjectProvider, you can call ReplaceData/ReplaceObject or TransferData/TransferObject to request changes to the live object. Just like the GetData/GetObject calls we discussed in earlier posts, the "Data" versions of the methods work against Stream objects and you must serialize/deserialize the values yourself, while the "Object" methods will call the "Data" method, then automatically serialize/deserialize for you.

Calls to IVisualizerObjectProvider.ReplaceData result in a call to VisualObjectSource.CreateReplacementObject on the app side. The intention of this API is to completely replace the live object with the version being passed into the ReplaceData call. If the object being visualized is Serializable, you can use the default implementation of VisualizerObjectSource.CreateReplacementObject, which will deserialize the incoming Stream and return the object as the new version. Otherwise, you'll need to write your own class that extends VisualizerObjectSource and overrides CreateReplacementObject to take in the Stream coming from the Visual Studio side (which could be a ViewModel object if the primary object isn't serializable) and convert it into an instance of the Type being visualized to replace the current object.

Additionally, you'll need to first verify that IVisualizerObjectProvider.IsObjectReplaceable is true. Otherwise, you won't be allowed to manipulate the object via the ReplaceData method. Documentation on when this would be false is scarce, but I did find this summary block for the property in a Patent filing for this feature:

public interface IVisualizerObjectProvider
{
    /// <summary>
    /// Specifies whether a replacement object can be created. For instance, if the object being
    /// visualized is a readonly field then this will return false.
    /// </summary>
    /// <returns></returns>
    bool IsObjectReplaceable { get; }

From https://www.google.com/patents/US7657873

Alternatively, you can use the TransferData method to send any arbitrary data stream from your visualizer to the VisualizerObjectSource.TransferData method. With this pair of methods, you can send serialized data from the Visual Studio side to the application side, and optionally have it reply back with a serialized object. This opens up the possibly of implementing a messaging system between the two side for more complex interactions.

For instance, in the case of a WebException, you could decide to use the actual WebException type in the GetData call, then, should the user click a button on the UI to review the response HTML, you could send a message to the application via the TransferData call, have it read the WebExceptionResponse.GetResponseStream() Stream and return the string version of the output.

NOTE: Communication is always initiated by the Visual Studio side -- specifically, calls from your Visualizer class into IVisualizerObjectProvider. The application-side cannot initiate the conversation and TransferData is the only method where it can return something other than the object being used by your visualizer.

The Logical Flow (For Modifying an Object from Your Visualizer)

The initial flow for getting data into your visualizer will remain the same (see the previous posts in this series). But once visualized, users can then interact with your UI to initiate changes to the object. Generally, this will be via OnChange-type events in your object/ViewModel or button click UI events that are handled by your Visualizer class. Thus, any changes or actions the user makes via the UI will be picked up by your Visualizer class.

Then, from your handler, you need to decide which API you want to use.

The simpler path would be to modify the version of the object you already have in memory (or have the UI bound directly to the object so it is automatically updated on changes), then call ReplaceObject with the updated object.

Otherwise, you can call TransferObject with some object of your choosing, such as a messaging DTO you've created that holds the action or changes you want to pass to the application.

This, in turn, will call the corresponding method in VisualizerObjectSource.

If you used ReplaceData or ReplaceObject, your implementation of CreateReplacementObject is called with the current object instance and the Stream of the serialized object coming from the IVisualizerObjectProvider. That method will need to take the incoming Stream and return an object. When the method returns the object, it will replace the original object in the applications memory.

If you used TransferData or TransferObject, your implementation of TransferData will be called. You cannot use the base implementation of TransferData -- it will just throw NotImplementedException). There are three parameters passed into VisualizerObjectSource.TransferData:

  • The original object being visualized
  • An incoming Stream for the data being passed from the visualizer TransferData
  • An outgoing Stream to allow a reply back to TransferData

The key for using TransferData is to have both sides of the call to have a common understanding of the types being passed around so that they can be deserialized/cast to the appropriate object Type rather than just object. Then it's just a matter of determining what data needs to be modified or what action needs to be taken and acting accordingly. Ultimately, the TransferData method needs to modify the passed-in original object if changes are needed.

In that case where TransferData is used, the third parameter passed into the VisualizerObjectSource.TransferData method is an outgoing Stream. This allows for a response to be returned to the Visualizer via the IVisualizerObjectProvider.TransferData's return value. For TransferData, the return value is a Stream which you must deserialize yourself, or if you used TransferObject, it's an object that you must cast to the appropriate Type. So again, having an common understanding of the types being transferred is key.

An example of how this might be used:

For a WebResponse visualizer, if you need the actual response value, you could call IVisualizerObjectProvider.TransferData with a message object requesting the actual response value. Then your VisualizerObjectSource.TransferData, upon decoding the message, would call Response.GetResponseStream() and read the response into a string object. Then, that string could be returned on the outgoing Stream. Then, the visualizer would receive the string as the return value of it's call to TransferData, and update the UI to display the string value.

Implementing a Read-Write Visualizer

Starting from the visualizer code at the end of my "Writing A (ReadOnly) Custom Debugger Visualizer for Visual Studio" post (which used a WinForms-based UI), I'll change a few things in the VisualizerObjectSource and Visualizer implementations.

The VisualizerObjectSource

TIP: If you use ReplaceData/ReplaceObject and you're passing the actual type being visualized (not a ViewModel), then you don't actually need to implement your own VisualizerObjectSource and/or override CreateReplacementObject, as the base implementation will just deserialize the passed-in object and replace the live object with the new version.

Otherwise, you'll override the CreateReplacementObject method to handle the conversion process. If you're passing a ViewModel into TransferData, then CreateReplacementObject will need to map the ViewModel's values into either a new instance of the type being visualized, or modify the original object (which is passed in as a parameter). Ultimately, the CreateReplacementObject call must return an object which will replace the live object being visualized in the application's memory.

WARNING: When using the ReplaceData calls, be aware of object pointers in your object and make sure you wire the pointers back up correctly. The serialization process may result in new objects being created as clones of your original objects and you'll need to use the CreateReplacementObject method to replace those with the original objects pointed to in your object graph.

    public override object CreateReplacementObject(object target, Stream incomingData)
    {
        var originalObject = target as SimpleExample.SomeType;

        var incomingChangedObject = Deserialize(incomingData) as SimpleExample.SomeType;
        // if this is a ViewModel, you'll need to map the value into either a new instance of 
        // the visualized type, or modify the originalObject with the ViewModel's values and return
        // the originalObject as the new instance.

        // Beware object pointers! If you have an object graph or other data types which will result in
        // cloned objects being created during the serialization/deserialization process, you'll need to
        // handle that here.

        //returns a instance of the object, which VS will substitute for the original object in memory
        return incomingChangedObject;           
    }

If you've used the TransferData/TransferObject, you must override the VisualizerObjectSource.TransferData method. As parameters, the method will receive the original object being visualized, the incoming Stream being sent from the IVisualizerObjectProvider, and an outbound Stream which can be used to send response messages/data back to the Visualizer.

    public override void TransferData(object target, Stream incomingData, Stream outgoingData)
    {
        var originalObject = target as SimpleExample.SomeType;

        var incomingChangedObject = Deserialize(incomingData) as SimpleExample.SomeType;

        // any changes to the object must be applied to the incoming target object instance
        originalObject.Foo = incomingChangedObject.Foo;

        //(optional) send a response message back to the Visualizer
        Serialize(outgoingData, "It worked!");            
    }

The Visualizer

For the visualizer in this simple example, I'm registering an OnChange event handler for the TextBox displaying the Foo property (the only property shown in this example) and I've removed the Readonly attribute on the textbox to allow the user to edit the value. I'm using in-line delegates as my handler, but you could just as well register explicit methods.

I'm including both approaches in this code sample, but you'll want to use one or the other, not both.

    protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
    {
        if (windowService == null)
            throw new ArgumentNullException(nameof(windowService));

        if (objectProvider == null)
            throw new ArgumentNullException(nameof(objectProvider));

        // Get the object to display a visualizer for.
        //       Cast the result of objectProvider.GetObject() 
        //       to the type of the object being visualized.
        var data = objectProvider.GetObject() as SimpleExample.SomeType;


        // Display your view of the object.        
        using (var displayForm = new SomeTypeVisualizerForm(data))
        {
            displayForm.txtFoo.Text = data.Foo;


            // Read-Write Approach 1: Using ReplaceObject
            displayForm.OnChange += (sender, newObject) => objectProvider.ReplaceObject(newObject);

            // Read-Write Approach 2: Using TransferData
            displayForm.OnChange += (sender, newObject) =>
            {
                var response = objectProvider.TransferObject(newObject) as string;
                if (!string.IsNullOrEmpty(response))
                {
                    MessageBox.Show(response, "Response", MessageBoxButtons.OK);
                }

            };

            windowService.ShowDialog(displayForm);
        }
    }

Make sure to take a look at the other posts in my Debugger Visualizer series to improve upon your visualizer.



This post builds upon the first post in the series Writing A (ReadOnly) Custom Debugger Visualizer for Visual Studio. Please at least skim through that post to understand the topics in this one.

Using WPF for the UI

Ok, so far my UIs have been pretty basic, but as I start to visualize more complex object types, I'm going to need more complex UIs. And personally, I find that hard to do with WinForms. Plus, I'd rather use MVVM-style UI binding instead of the code-behind style typical with WinForms. So, I want to use WPF for my UI instead of WinForms.

Thankfully, this is super easy to do!

I'll use the same ViewModel and VizualizerObjectSource as I did for my WinForms visualizer, but on the Visual Studio side, I'll have a different UI and a different Visualizer class:

The UI

Obviously, the UI will be WPF-based now. I'll use DataContext binding in my XAML to bind directly against my ViewModel class.

<UserControl x:Class="TotallyRealApp.DebuggerVisualizer.WebException.WebExceptionVisualizerControl"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:local="clr-namespace:TotallyRealApp.DebuggerVisualizer.WebException"             
            mc:Ignorable="d" 
            d:DesignHeight="500" d:DesignWidth="500"             
            MinWidth="500" MinHeight="500" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            d:DataContext="{d:DesignInstance Type=local:WebExceptionViewModel, IsDesignTimeCreatable=False}"
            Padding="5" Margin="5">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" MinWidth="500"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="0 5" Grid.Row="0">
            <Label Margin="0 0 5 0" Padding="0" FontWeight="Bold">Message:</Label>
            <TextBlock TextWrapping="Wrap" Text="{Binding Path=Message, Mode=OneWay}" Height="auto" />
        </StackPanel>           

        <Label  Grid.Row="1" Margin="0 5" Padding="0" FontWeight="Bold">Stack Trace:</Label>

        <ScrollViewer Grid.Row="2"  VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto">
            <TextBlock Width="Auto" TextWrapping="Wrap" Text="{Binding Path=StackTrace, Mode=OneWay}"></TextBlock>
        </ScrollViewer>

        <Label Margin="0 0 5 0" Padding="0 5" FontWeight="Bold" Grid.Row="3">Raw Response:</Label>

        <ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" Grid.Row="4">
            <TextBlock TextWrapping="NoWrap" Text="{Binding Path=Response.RawResponse, Mode=OneWay}" Width="Auto"></TextBlock>
        </ScrollViewer>


    </Grid>
</UserControl>

The Visualizer

The other change that's needed to use WPF is to the Visualizer. Instead of using the passed-in IDialogVisualizerService to show a dialog, we'll create an instance of a WPF Window, put our WPF UserControl on it, and show that window.

public class WpfWebExceptionVisualizer : DialogDebuggerVisualizer
{
    protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
    {          
        if (objectProvider == null)
            throw new ArgumentNullException(nameof(objectProvider));

        // Get the object to display a visualizer for.
        //       Cast the result of objectProvider.GetObject() 
        //       to the type of the object being visualized.
        var data = objectProvider.GetObject() as WebExceptionViewModel;

        // Display your view of the object.                       
        var vizControl = new WebExceptionVisualizerControl { DataContext = data };

        // set the attributes of WPF window
        var win = new Window
        {
            Title = "WPF Web Exception Visualizer",
            MinWidth = 500,
            MinHeight = 500,
            Padding = new Thickness(5),
            Margin = new Thickness(5),
            WindowStartupLocation = WindowStartupLocation.CenterScreen,
            Content = vizControl,
            VerticalContentAlignment = VerticalAlignment.Stretch,
            HorizontalContentAlignment = HorizontalAlignment.Stretch
        };

        win.ShowDialog();
    }
}

Make sure to take a look at the other posts in my Debugger Visualizer series to improve upon your visualizer.



This post builds upon the first post in the series Writing A (ReadOnly) Custom Debugger Visualizer for Visual Studio. Please at least skim through that post to understand the topics in this one.

Non-Serializable Types and/or More Complex Objects

In the real world, our data types aren't super-simple, and it's likely you'll want to visualize an object (or object graph) that has non-serializable parts. The first visualizer I wrote actually falls into this case. I wanted to visualize WebException, which is actually Serializable, but the main thing I wanted access to (which I can't get in the built-in debugger tooling) is the actual HTML response returned from the website.

Consider this scenario:

You have an ASP.NET WebAPI site and an application/website that calls that API from C# code. The WebAPI throws a 500 error. When this happens (assuming you've turned on the appropriate configuration options), the returned HTML includes stacktraces and other helpful information. But since it's a non-successful response code, the call results in a WebException being thrown in the calling code. I often want to look at the raw returned response to get additional details, which is exposed as a Stream from the WebException.GetResponseStream() method -- which isn't included in any serialized instance of the exception.

So, my visualizer can't just pass the WebException object through the VisualizerObjectSource and get the raw response content. Instead, I need to extract the information I want passed over to my visualizer and return my own class with the information. Think of this as the ViewModel of your visualizer -- it exposes the members of the Model (the exception) to your View (the visualiser UI) in an easily consumable way.

To do this, I need to get a little more advanced with my components:

My ViewModel classes

I've split the ViewModel into two classes: One for the WebException and one for the WebResponse object it contains. I did this in order to make the WebResponse viewmodel reusable in other visualizers.

[Serializable]
public class WebExceptionViewModel
{
    private readonly ResponseViewModel _response;

    /// <summary>
    /// The original Exception
    /// </summary>
    private readonly System.Net.WebException _exception;

    public WebExceptionViewModel(System.Net.WebException ex)
    {
        _exception = ex;
        if (ex.Response != null)
        {
            _response = new ResponseViewModel(ex.Response);
        }
    }

    public bool HasResponse => _response != null;

    public ResponseViewModel Response => _response;

    public string Message => _exception.Message;

    public string StackTrace => _exception.ToString();

    public string Status => _exception.Status.ToString();
}


[Serializable]
public class ResponseViewModel
{
    public long ContentLength { get; } 
    public string ContentType { get; } 
    public string ResponseUri { get; } 
    public string RawResponse { get; }

    public ResponseViewModel(System.Net.WebResponse response)
    {            
        ContentLength = response.ContentLength;
        ContentType = response.ContentType;
        ResponseUri = response.ResponseUri.ToString();

        var stream = response.GetResponseStream();
        if (stream != null)
        {
            // save position before reading
            long position = stream.Position;
            stream.Seek(0, SeekOrigin.Begin);

            using (var readStream = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true))
            {
                RawResponse = readStream.ReadToEnd();                    
            }

            // reset seek position so we don't have side effects on the app
            stream.Seek(position, SeekOrigin.Begin);
        }            
    }
}

The key piece is the part of the WebResponse constructor that reads the response Stream and stores the results as a string property. Now, that data will be included in the serialized object that is passed back to the visualizer UI.

IMPORTANT NOTE: Did you notice how I store off the current stream position and restore it after I'm done -- and that I don't close the stream? That's because I don't want my visualizer to have side effects on the application being debugged. If the application's try/catch block is trying to read that stream too, and I happened to pause the debugger and visualize the object part-way through that, I need to make sure the app can continue running after I close my visualizer and unpause the debugging session. Keep this in mind when writing your own visualizers -- if you modify the object in any way, then when the app starts running again, it will be using the modified version of the object. (We'll do this on purpose in my next post when we make a read-write visualizer.)

Visualizer Object Source

Now that I've got a ViewModel to wrap around my primary object, I can no longer use the built-in VisualizerObjectSource -- I need to have a customized VisualizerObjectSource class that will return the view model instead of the original object being visualized.

Recall that VisualizerObjectSource has a GetData() method that's called to fetch the serialized object to display. So I've written a class that extends VisualizerObjectSource and overridden the GetData() implementation to use the view model class instead of the exception class. I countinue to use the base implementation to do the actual serialization, but I don't have to -- I could write directly to the outgoingData Stream and return.

public class WebExceptionVisualObjectSource : VisualizerObjectSource
{
    public override void GetData(object target, Stream outgoingData)
    {
        var originalObj = target as System.Net.WebException;
        var viewModel = new WebExceptionViewModel(originalObj);

        base.GetData(viewModel, outgoingData);
    }
}

The UI

My UI is still just a simple WinForm with a few TextBox controls on it:

The Visualizer

My Visualizer class still calls objectProvider.GetObject(), but now casts the result as my WebExceptionViewModel class. Then I again create my WinForms UI object, set the value and display it.

public class SimpleWebExceptionVisualizer : DialogDebuggerVisualizer
{
    protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
    {
        if (windowService == null)
            throw new ArgumentNullException(nameof(windowService));

        if (objectProvider == null)
            throw new ArgumentNullException(nameof(objectProvider));

        // Get the object to display a visualizer for.
        //       Cast the result of objectProvider.GetObject() 
        //       to the type of the object being visualized.
        var data = objectProvider.GetObject() as WebExceptionViewModel;

        // Display your view of the object.           
        using (var displayForm = new SimpleWebExceptionWinForm())
        {
            if (data == null)
            {
                displayForm.txtMessage.Text = "<null>";                    
            }
            else
            {
                displayForm.Text = objectProvider.GetType().FullName; // TitleBar
                displayForm.txtMessage.Text = data.Message; 
                displayForm.txtStackTrace.Text = data.StackTrace;
                displayForm.txtResponse.Text = data.HasResponse ? data.Response.RawResponse : "<null>";
            }

            windowService.ShowDialog(displayForm);
        }
    }        
}

Registering The Visualizer

I'll use the DebuggerVisualizer attribute again to register my new debugger, but this time I'm not using the built-in VizualizerObjectSource, so I need to tell the Debugger which class to use as the 2nd parameter to the attribute:

[assembly: DebuggerVisualizer(
    typeof(TotallyRealApp.DebuggerVisualizer.SimpleWebExceptionVisualizer), //Your VS-side type
    typeof(WebExceptionVisualObjectSource), //the incoming type's provider -- default is ok if Type is serializable
    Target = typeof(WebException), //the type you want to visualize
    Description = "Simple WebException Visualizer")] //name shown in visualizer picker

And now I can see the full HTML of the result:

More Advanced Topic: Using WPF for the UI

Ok, so far my UIs have been pretty basic, but as I start to visualize more complex object types, I'm going to need more complex UIs. And personally, I find that hard to do with WinForms. Plus, I'd rather use MVVM-style UI binding instead of the code-behind style typical with WinForms. So, I want to use WPF for my UI instead of WinForms.

Thankfully, this is super easy to do!

I'll use the same ViewModel and VizualizerObjectSource as I did for my WinForms visualizer, but on the Visual Studio side, I'll have a different UI and a different Visualizer class:

The UI

Obviously, the UI will be WPF-based now. I'll use DataContext binding in my XAML to bind directly against my ViewModel class.

<UserControl x:Class="TotallyRealApp.DebuggerVisualizer.WebException.WebExceptionVisualizerControl"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:local="clr-namespace:TotallyRealApp.DebuggerVisualizer.WebException"             
            mc:Ignorable="d" 
            d:DesignHeight="500" d:DesignWidth="500"             
            MinWidth="500" MinHeight="500" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            d:DataContext="{d:DesignInstance Type=local:WebExceptionViewModel, IsDesignTimeCreatable=False}"
            Padding="5" Margin="5">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" MinWidth="500"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="0 5" Grid.Row="0">
            <Label Margin="0 0 5 0" Padding="0" FontWeight="Bold">Message:</Label>
            <TextBlock TextWrapping="Wrap" Text="{Binding Path=Message, Mode=OneWay}" Height="auto" />
        </StackPanel>           

        <Label  Grid.Row="1" Margin="0 5" Padding="0" FontWeight="Bold">Stack Trace:</Label>

        <ScrollViewer Grid.Row="2"  VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto">
            <TextBlock Width="Auto" TextWrapping="Wrap" Text="{Binding Path=StackTrace, Mode=OneWay}"></TextBlock>
        </ScrollViewer>

        <Label Margin="0 0 5 0" Padding="0 5" FontWeight="Bold" Grid.Row="3">Raw Response:</Label>

        <ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" Grid.Row="4">
            <TextBlock TextWrapping="NoWrap" Text="{Binding Path=Response.RawResponse, Mode=OneWay}" Width="Auto"></TextBlock>
        </ScrollViewer>


    </Grid>
</UserControl>

The Visualizer

The other change that's needed to use WPF is to the Visualizer. Instead of using the passed-in IDialogVisualizerService to show a dialog, we'll create an instance of a WPF Window, put our WPF UserControl on it, and show that window.

public class WpfWebExceptionVisualizer : DialogDebuggerVisualizer
{
    protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
    {          
        if (objectProvider == null)
            throw new ArgumentNullException(nameof(objectProvider));

        // Get the object to display a visualizer for.
        //       Cast the result of objectProvider.GetObject() 
        //       to the type of the object being visualized.
        var data = objectProvider.GetObject() as WebExceptionViewModel;

        // Display your view of the object.                       
        var vizControl = new WebExceptionVisualizerControl { DataContext = data };

        // set the attributes of WPF window
        var win = new Window
        {
            Title = "WPF Web Exception Visualizer",
            MinWidth = 500,
            MinHeight = 500,
            Padding = new Thickness(5),
            Margin = new Thickness(5),
            WindowStartupLocation = WindowStartupLocation.CenterScreen,
            Content = vizControl,
            VerticalContentAlignment = VerticalAlignment.Stretch,
            HorizontalContentAlignment = HorizontalAlignment.Stretch
        };

        win.ShowDialog();
    }
}

Make sure to take a look at the other posts in my Debugger Visualizer series to improve upon your visualizer.



Doing it the hard way!

(image source)

You've hit your Visual Studio breakpoint, opened the watch window and you're looking at a wall of nested collections and object graphs. You know the data you want is in there, but digging it out of the built-in data table UI feels like the "doing it the hard way" part of an infomercial.

Now, imagine being able to shape your data the way that makes it easiest to understand and debug, limited only by your imagination (and time, of course).

This is the potential embodied in the Visual Studio Debugger Visualizer feature, available in all version of Visual Studio on Windows (even the free Community edition!) that lets you create your own graphical display of your .NET data structures while debugging. Shape the data the way you want to make it easier to digest -- hide unimportant info, quickly expose deeply nested elements, compare large object graphs, or even instantly search Stack Overflow for help with an Exception. Display it in whatever way is best for your data and your understanding of it, act on the data and even modify the it from your custom view.

Backup! What's a "Debugger Visualizer" Anyway?

You've probably already used the Debugger Visualizer feature with one of the built-in visualizers that ship with Visual Studio.

When debugging from within Visual Studio, you will at times see a small spyglass icon alongside data in the Locals, Auto, Watch and QuickWatch windows. This is the entry point to any visualizers that are available for the current item's Type. Clicking the arrow will show you a drop-down menu with all of the registered visualizers for that type, while clicking the spyglass itself will start the default/last used visualizer for that Type.

In the above screenshot, you can see the four built-in visualizers for the System.String type:

  • Text
  • XML
  • HTML
  • JSON

The Text visualizer just displays the string, in it's entirety, in a window. This is particularly useful when you have very long strings, such as exception stack traces. The others will attempt to format the string based on the data structures they represent.

Text Visualizer (with a string of HTML shown):

HTML Visualizer will display the string in a web browser control:

The JSON and XML visualizers will provide collapsible areas for nested data within the structure being visualized:

Additionally, Visual Studio has a WPF Tree visualizer and a DataSet/DataView/DataTable visualizer.

In each case, the visualizer makes it easier to interpret and understand the data -- well beyond what you can do from the small snippet of a string you see in the watch windows directly.

Going Beyond The Basics

Ok, so you can visualize a string -- great. But if you want to visualize a custom data type, or do something more complex, you're going to need to build your own (or use one someone else has built -- but see the Security Considerations section first!).

There's an existing page on the Microsoft documentation site on Writing a Visualizer in C#, but I found it hard to digest without understanding all the parts already, so my goal here is to break it down into something a bit easier to understand.

In this post, I'll walk through creating a read-only vizualizer. I'll expand upon this base in the next post and show how to create a vizualizer that lets you modify the object you're viewing.

The Components Involved

Ultimately, there are six components required for a custom debugger visualizer, in addition to Visual Studio and your app itself. However, for some cases, Microsoft provides implementations of the components, so you don't have to do the work.

The components fall into two buckets: Those that run within Visual Studio's debugger and those that run within your application (the one you're debugging).

Note: There are the minimum components you need. Later in this post, I'll touch on a few additional class you may want to add to make your implementation easier.

Bucket 1: Your Application

The first two components fall into the application side of the equation:

  • The Type you want to visualize.
    • This one's easy. You want to visualize a type -- so you need that type. It can be any type: one you've created, one within the .NET framework, or a third-party library. You don't need access to the source code, as long as what you want to show in the visualizer is available from the object's members to which you have access.
      The one requirement: It must be Serializable. If it's not (and you can't make it serializable), don't worry -- I'll talk about using a ViewModel class as an alternative in my Writing A Custom Debugger Visualizer for Non-Serializable Types or More Complex Objects post.
  • A VisualizerObjectSource object.
    • This is a class that extends the Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource class (which is in the Microsoft.VisualStudio.DebuggerVisualizers assembly and installed with Visual Studio). The purpose of this object is to provide a serialized version of the Type upon request from a VisualizerObjectProvider (you'll see this in a second under the Visual Studio bucket). There are only three methods in this class (with two additional static helper methods) and I'll talk about all of them below.

Bucket 2: Visual Studio

There are four additional components on the Visual Studio side:

  • The UI form/control
  • An IDialogVisualizerService instance.
    • This is the easiest of the all, because Visual Studio will give it to you as a parameter when calling your VisualizerObjectProvider (see below). It consists only of three overloads of a ShowDialog method, each taking a different Form or Control type and displaying it.
  • An IVisualizerObjectProvider instance.
    • This too is provided as a parameter by Visual Studio, so you don't need to implement your own. It acts as the communication between the Visual Studio bucket and your application. A call into this object results in a specific method being called on the VisualizerObjectSource object on the other side.
  • A class that extends DialogDebuggerVisualizer
    • This is where everything is glued together. When a user selects a visualizer to run, this is the code that gets called. It has one method you must implement: Show. Visual Studio will pass you the IDialogVisualizerService and IVisualizerObjectProvider and you run with it from there.

The Logical Flow (For a Read-Only Visualizer)

Now that we have all the components defined, here's how they all work together:

When the user clicks the spyglass icon within Visual Studio, the selected Visualizer's Show method is called.
Visual Studio will pass into it an instance of IDialogVisualizerService and IVisualizerObjectProvider.

Within your Show method, you'll use the IVisualizerObjectProvider to request the live object from your application.
You have two method options to do this:

  • GetData(), which will return a Stream which you must deserialize yourself into the type being transferred.
  • GetObject(), which will call GetData() and deserialize the stream for you and return an object (which you can cast to the type being transferred).

When you call GetData (directly or via GetObject), that will result in a call to GetData in the VisualizerObjectSource you've implemented on the application side.

The VisualizerObjectSource.GetData() method has two parameters (and no return type): - The live instance of the object being visualized. (This will come in as an object type, so you'll need to cast it to the type if you need to access it directly). - A "outgoingdata" Stream.

Within this method, you'll need to serialize the live object and feed it onto the outgoingdata Stream. This stream is what is returned by IVisualizerObjectProvider.GetData() on the Visual Studio side.

Tip: The VisualizerObjectSource class has two static helper methods (Serialize and Deserialize) to make it easier for you if you decide not to use GetObject.

Now that you have a deserialized instance of your live object, you'll create an instance of your UI control and populate it with that object's data. Then, call IDialogVisualizerService.ShowDialog, passing it your UI control. This will show the form to the user.

Implementing a Read-Only Visualizer

Enough talk -- let's see some code!

Note: I'm going to create a very simple visualizer here to show the mechanics and reduce noise. I'll expand on this in the other posts in this series.

You can find code samples to go along with this post and others in this series in my DebugVisualizer GitHub repository.

The Type To Visualize

Here's the Type I want to visualize. It's got a string member named Foo that's public (so it's accessible by my visualizer) and it's marked as [Serializable].

using System;

namespace SimpleExample
{   
    [Serializable]
    public class SomeType
    {
        public string Foo { get; set; }
    }       
}

The VisualizerObjectSource

Now, here's a little secret: If you're type you want to visualize is serializable and you're only creating a read-only visualizer, you don't actually need to implement your own VisualizerObjectSource. You can use the actual VisualizerObjectSource class itself, which will serialize your object using the static helper method VisualizerObjectSource.Serialize (which, in turn, uses .NET's BinaryFormatter.Serialize).

The UI

I've created a very simple WinForms Form with a Label and a TextBox named txtFoo. I've made the TextBox readonly and set the Modifiers value to Internal so my visualizer can set it directly.

The Visualizer

The final piece needed for this very simple visualizer is the actual visualizer class. I'm creating a class (SomeTypeVisualizer) that extends DialogDebuggerVisualizer and provided an implementation for the abstract Show() method. In the Show method, I do the following:

  • call GetObject() on the objectProvider to get an instance of the object to visualize and cast it to my SomeType type.
  • create an instance of my UI form and populate the UI's textbox with the value from my object.
  • Call ShowDialog on the IDialogVisualizerService, passing it my UI control.

    using System;
    using Microsoft.VisualStudio.DebuggerVisualizers;
    
    namespace SimpleExample.DebuggerVisualizer.SomeType
    {   
        /// <summary>
        /// A Visualizer for SomeType.  
        /// </summary>
        public class SomeTypeVisualizer : DialogDebuggerVisualizer
        {
            protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
            {
                if (windowService == null)
                    throw new ArgumentNullException(nameof(windowService));
    
                if (objectProvider == null)
                    throw new ArgumentNullException(nameof(objectProvider));
    
                // Get the object to display a visualizer for.
                //       Cast the result of objectProvider.GetObject() 
                //       to the type of the object being visualized.
                var data = objectProvider.GetObject() as SimpleExample.SomeType;
    
                 // Display your view of the object.
                using (var displayForm = new SomeTypeVisualizerForm())
                {
                    displayForm.txtFoo.Text = data.Foo;
                    windowService.ShowDialog(displayForm);
                }
            }
    
        }
    }
    

Registering the Visualizer

The final step is letting Visual Studio know that I've created a visualizer and when it should be used. This is done using the System.Diagnostics.DebuggerVisualizer attribute in one of two ways:

  • On the SomeType class itself.
  • At the assembly level.

In it's simplest form, The DebuggerVisualizerAttribute takes in the type of the visualizer, either as a Type or as a string of the fully-qualified type name. Since this is my super-simple example, I'm going to use that register it on the SomeType class itself by adding the DebuggerVisualizer attribute:

[DebuggerVisualizer(typeof(SomeTypeVisualizer))]
[Serializable]
public class SomeType
{
    public string Foo { get; set; }
}

Alternatively, I could register it at the assembly level by including this attribute somewhere in my assembly. If you do this, In this instance, you have to provide the Target type to let Visual Studio know what type this visualizer is being registered for.

[assembly: DebuggerVisualizer(typeof(SomeTypeVisualizer), Target=typeof(SomeType))]

Since my visualizer is defined in the same solution as I want to use it, I don't have to install it anywhere. If I wanted to have a set of visualizers in a separate assembly that are always available, I'd need to install them into a known location that Visual Studio searches (see the Installation, Limitations, and Security Considerations post).

Testing The Visualizer

If you want to be able to test your visualizer UI without actually having to debug an app, Visual Studio provides a way to do that using the VisualizerDevelopmentHost class. This class has a few different constructors for providing the object you want to visualize, your visualizer, and optionally the VisualizerObjectSource type. In our simple case where we're using the default VisualizerObjectSource, we can use it like this:

 VisualizerDevelopmentHost visualizerHost =  new VisualizerDevelopmentHost(objectToVisualize, typeof(SomeTypeVisualizer));           
 visualizerHost.ShowVisualizer();

Throw that into a console app and you're good to go.

And now I can use my simple visualizer anytime an object of SomeType is available in the debugger:

Bam! We're done -- at least with a simple example. But you've got the basics now and just need to apply it to your own types.

Make sure to take a look at the other posts in my Debugger Visualizer series.