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.