Visual Studio exception visualizer for lengthy exception messages











up vote
5
down vote

favorite
1












I use Autofac a lot and for everything and when you make a mistake and forget to register a dependency etc. it'll tell you exaclty what's wrong. Although its exceptions are very helpful, the exception strings are at the same time hard to read because it's a large blob of text:




System.Exception: Blub ---> Autofac.Core.DependencyResolutionException: An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope ---> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'. (See inner exception for details.) ---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'.
at Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(IComponentContext context, IEnumerable`1 parameters)




To find the reason for this exception in such a string isn't easy. This is better done by some tool so I created. It reads the string for me and presents it in a more friendly way. I implemented it as a Debugger Visualizer.



Screenshot-1





DebuggerVisualizer



The ExceptionVisualizer is virtually a single function that shows the WPF window with the exception strings:



public class ExceptionVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var data = (IEnumerable<ExceptionInfo>)objectProvider.GetObject();

var window = new Window
{
Title = "Exception Visualizer",
Width = SystemParameters.WorkArea.Width * 0.4,
Height = SystemParameters.WorkArea.Height * 0.6,
Content = new DebuggerVisualizers.ExceptionControl
{
DataContext = new ExceptionControlModel
{
Exceptions = data
},
HorizontalAlignment = HorizontalAlignment.Stretch
},
WindowStartupLocation = WindowStartupLocation.CenterScreen
};

window.ShowDialog();
}

public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}


It receives a collection of ExceptionInfos that I create with these helpers that parse the string by removing the stack trace and extracting exception names and messages. I know I could use the Exception object to extract exception names and messages but I'm going to reuse this parser in another tools later for parsing and searching logs so I didn't want to have two solutions.



public class ExceptionParser
{
public static string RemoveStackStrace(string exceptionString)
{
// Stack-trace begins at the first 'at'
return Regex.Split(exceptionString, @"^s{3}at", RegexOptions.Multiline).First();
}

public static IEnumerable<ExceptionInfo> ParseExceptions(string exceptionString)
{
// Exceptions start with 'xException:' string and end either with '$' or '--->' if an inner exception follows.
return
Regex
.Matches(exceptionString, @"(?<exception>(^|w+)?Exception):s(?<message>(.|n)+?)(?=( --->|$))", RegexOptions.ExplicitCapture)
.Cast<Match>()
.Select(m => new ExceptionInfo { Name = m.Groups["exception"].Value, Message = m.Groups["message"].Value });
}
}

public static class EnumerableExtensions
{
public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source) => new Stack<T>(source);
}


The DTO is



[Serializable]
public class ExceptionInfo
{
public string Name { get; set; }

public string Message { get; set; }

public override string ToString()
{
return Name + Environment.NewLine + Message;
}
}




GUI



On the UI side there is simple a WPF.UserControl with a ListBox and two TextBoxes. The Close button closes the window and the Copy button copies the list to Clipboard and contains a small animation that lets the button shrink and grow back to its original size. In order to keep it in the middle I also animate the right Margin.



<UserControl x:Class="Reusable.Apps.ExceptionControl"
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:Reusable.Apps"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="#FF404040"
>
<UserControl.Resources>
<local:ExceptionControlModel x:Key="DesignViewModel" />
<Style TargetType="TextBlock" x:Key="NameStyle">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="DarkOrange"/>
<Setter Property="Margin" Value="0,10,0,0" />
</Style>
<Style TargetType="TextBlock" x:Key="MessageStyle">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Foreground" Value="WhiteSmoke"/>
</Style>
<Style x:Key="Theme" TargetType="{x:Type Control}">
<Setter Property="Background" Value="#FF404040"></Setter>
</Style>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Close"></CommandBinding>
</UserControl.CommandBindings>
<Grid >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Style="{StaticResource Theme}" Grid.Row="0" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Width" Value="{Binding (Grid.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border>
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
</Border>
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DockPanel Grid.Row="1" HorizontalAlignment="Right" >
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,5,10,5" />
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"></Setter>
<Setter Property="FontSize" Value="15"/>
</Style>
</DockPanel.Resources>
<Button
Content="Copy"
Command="{x:Static local:ExceptionControlModel.CopyCommand}"
CommandParameter="{Binding}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="90" Duration="0:0:0.25"/>
<DoubleAnimation Storyboard.TargetProperty="Width" From="90" To="100" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,10,5" To="0,5,15,5" Duration="0:0:0.25"/>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,15,5" To="0,5,10,5" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button
Content="Close"
Command="{x:Static local:ExceptionControlModel.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</DockPanel>
</Grid>
</UserControl>


and this is its model with some design-time data:



public class ExceptionControlModel
{
public static readonly ICommand CloseCommand = CommandFactory<Window>.Create(p => p.Close());

public static readonly ICommand CopyCommand = CommandFactory<ExceptionControlModel>.Create(p => p.CopyToClipboard());

public IEnumerable<ExceptionInfo> Exceptions { get; set; } = new
{
// This is design-time data.
new ExceptionInfo {Name = "DependencyResolutionException", Message = "An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope"},
new ExceptionInfo {Name = "DependencyResolutionException", Message = "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters: Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'."},
};

private void CopyToClipboard()
{
Clipboard.SetText(Exceptions.Join(Environment.NewLine + Environment.NewLine));
}
}


The command creation is supported with this helper factory that passes a strong type to the handler delegate:



public static class CommandFactory<T>
{
public static ICommand Create([NotNull] Action<T> execute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));

return new Command(parameter => execute((T)parameter));
}

public static ICommand Create([NotNull] Action<T> execute, [NotNull] Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));
if (canExecute == null) throw new ArgumentNullException(nameof(canExecute));

return new Command(parameter => execute((T)parameter), parameter => canExecute((T)parameter));
}

private class Command : ICommand
{
private readonly Action<object> _execute;

private readonly Predicate<object> _canExecute;

public Command(Action<object> execute) : this(execute, _ => true) { }

public Command(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

#region ICommand

public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter) => _canExecute(parameter);

[DebuggerStepThrough]
public void Execute(object parameter) => _execute(parameter);

#endregion
}
}




Example



I use the following code for testing where I try to resolve an instance of the User class which is missing a dependency:



internal class ExceptionVisualizerExperiment
{
public static void Run()
{
try
{
try
{
var builder = new ContainerBuilder();
builder.RegisterType<User>();
var container = builder.Build();
container.Resolve<User>();
throw new DivideByZeroException("Blub");
}
catch (Exception ex)
{
throw new Exception("Blub", ex);

}
}
catch (Exception ex)
{
var exceptionString = ex.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString);
ExceptionVisualizer
.TestShowVisualizer(EnumerableExtensions.Reverse(exceptions));
}
}


public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}

internal class User
{
public User(string name) { }
}




Running



There is one more component that is required to run it in Visual Studio. It's the custom object-source for the exception serialization:



public class ExceptionVisualizerObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var exceptionString = target.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString).Reverse();
Serialize(outgoingData, exceptions);
}
}


This needs to be registered with:



[assembly: DebuggerVisualizer(
visualizer: typeof(ExceptionVisualizer),
visualizerObjectSource: typeof(ExceptionVisualizerObjectSource),
Target = typeof(Exception),
Description = "Exception Visualizer")]




So what do you think about the parsing of the excpetion string and the UI? It's my first WPF application for a long time so it's probably not the state of the art. Is there anything you would improve either in the back and or front end?





As always, you can also find it on my GitHub under Reusable.DebuggerVisualizers. The Console code is here.










share|improve this question




















  • 1




    Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
    – Xiaoy312
    Dec 3 at 23:11






  • 1




    @Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
    – t3chb0t
    Dec 4 at 7:27















up vote
5
down vote

favorite
1












I use Autofac a lot and for everything and when you make a mistake and forget to register a dependency etc. it'll tell you exaclty what's wrong. Although its exceptions are very helpful, the exception strings are at the same time hard to read because it's a large blob of text:




System.Exception: Blub ---> Autofac.Core.DependencyResolutionException: An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope ---> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'. (See inner exception for details.) ---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'.
at Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(IComponentContext context, IEnumerable`1 parameters)




To find the reason for this exception in such a string isn't easy. This is better done by some tool so I created. It reads the string for me and presents it in a more friendly way. I implemented it as a Debugger Visualizer.



Screenshot-1





DebuggerVisualizer



The ExceptionVisualizer is virtually a single function that shows the WPF window with the exception strings:



public class ExceptionVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var data = (IEnumerable<ExceptionInfo>)objectProvider.GetObject();

var window = new Window
{
Title = "Exception Visualizer",
Width = SystemParameters.WorkArea.Width * 0.4,
Height = SystemParameters.WorkArea.Height * 0.6,
Content = new DebuggerVisualizers.ExceptionControl
{
DataContext = new ExceptionControlModel
{
Exceptions = data
},
HorizontalAlignment = HorizontalAlignment.Stretch
},
WindowStartupLocation = WindowStartupLocation.CenterScreen
};

window.ShowDialog();
}

public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}


It receives a collection of ExceptionInfos that I create with these helpers that parse the string by removing the stack trace and extracting exception names and messages. I know I could use the Exception object to extract exception names and messages but I'm going to reuse this parser in another tools later for parsing and searching logs so I didn't want to have two solutions.



public class ExceptionParser
{
public static string RemoveStackStrace(string exceptionString)
{
// Stack-trace begins at the first 'at'
return Regex.Split(exceptionString, @"^s{3}at", RegexOptions.Multiline).First();
}

public static IEnumerable<ExceptionInfo> ParseExceptions(string exceptionString)
{
// Exceptions start with 'xException:' string and end either with '$' or '--->' if an inner exception follows.
return
Regex
.Matches(exceptionString, @"(?<exception>(^|w+)?Exception):s(?<message>(.|n)+?)(?=( --->|$))", RegexOptions.ExplicitCapture)
.Cast<Match>()
.Select(m => new ExceptionInfo { Name = m.Groups["exception"].Value, Message = m.Groups["message"].Value });
}
}

public static class EnumerableExtensions
{
public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source) => new Stack<T>(source);
}


The DTO is



[Serializable]
public class ExceptionInfo
{
public string Name { get; set; }

public string Message { get; set; }

public override string ToString()
{
return Name + Environment.NewLine + Message;
}
}




GUI



On the UI side there is simple a WPF.UserControl with a ListBox and two TextBoxes. The Close button closes the window and the Copy button copies the list to Clipboard and contains a small animation that lets the button shrink and grow back to its original size. In order to keep it in the middle I also animate the right Margin.



<UserControl x:Class="Reusable.Apps.ExceptionControl"
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:Reusable.Apps"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="#FF404040"
>
<UserControl.Resources>
<local:ExceptionControlModel x:Key="DesignViewModel" />
<Style TargetType="TextBlock" x:Key="NameStyle">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="DarkOrange"/>
<Setter Property="Margin" Value="0,10,0,0" />
</Style>
<Style TargetType="TextBlock" x:Key="MessageStyle">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Foreground" Value="WhiteSmoke"/>
</Style>
<Style x:Key="Theme" TargetType="{x:Type Control}">
<Setter Property="Background" Value="#FF404040"></Setter>
</Style>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Close"></CommandBinding>
</UserControl.CommandBindings>
<Grid >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Style="{StaticResource Theme}" Grid.Row="0" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Width" Value="{Binding (Grid.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border>
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
</Border>
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DockPanel Grid.Row="1" HorizontalAlignment="Right" >
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,5,10,5" />
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"></Setter>
<Setter Property="FontSize" Value="15"/>
</Style>
</DockPanel.Resources>
<Button
Content="Copy"
Command="{x:Static local:ExceptionControlModel.CopyCommand}"
CommandParameter="{Binding}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="90" Duration="0:0:0.25"/>
<DoubleAnimation Storyboard.TargetProperty="Width" From="90" To="100" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,10,5" To="0,5,15,5" Duration="0:0:0.25"/>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,15,5" To="0,5,10,5" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button
Content="Close"
Command="{x:Static local:ExceptionControlModel.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</DockPanel>
</Grid>
</UserControl>


and this is its model with some design-time data:



public class ExceptionControlModel
{
public static readonly ICommand CloseCommand = CommandFactory<Window>.Create(p => p.Close());

public static readonly ICommand CopyCommand = CommandFactory<ExceptionControlModel>.Create(p => p.CopyToClipboard());

public IEnumerable<ExceptionInfo> Exceptions { get; set; } = new
{
// This is design-time data.
new ExceptionInfo {Name = "DependencyResolutionException", Message = "An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope"},
new ExceptionInfo {Name = "DependencyResolutionException", Message = "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters: Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'."},
};

private void CopyToClipboard()
{
Clipboard.SetText(Exceptions.Join(Environment.NewLine + Environment.NewLine));
}
}


The command creation is supported with this helper factory that passes a strong type to the handler delegate:



public static class CommandFactory<T>
{
public static ICommand Create([NotNull] Action<T> execute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));

return new Command(parameter => execute((T)parameter));
}

public static ICommand Create([NotNull] Action<T> execute, [NotNull] Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));
if (canExecute == null) throw new ArgumentNullException(nameof(canExecute));

return new Command(parameter => execute((T)parameter), parameter => canExecute((T)parameter));
}

private class Command : ICommand
{
private readonly Action<object> _execute;

private readonly Predicate<object> _canExecute;

public Command(Action<object> execute) : this(execute, _ => true) { }

public Command(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

#region ICommand

public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter) => _canExecute(parameter);

[DebuggerStepThrough]
public void Execute(object parameter) => _execute(parameter);

#endregion
}
}




Example



I use the following code for testing where I try to resolve an instance of the User class which is missing a dependency:



internal class ExceptionVisualizerExperiment
{
public static void Run()
{
try
{
try
{
var builder = new ContainerBuilder();
builder.RegisterType<User>();
var container = builder.Build();
container.Resolve<User>();
throw new DivideByZeroException("Blub");
}
catch (Exception ex)
{
throw new Exception("Blub", ex);

}
}
catch (Exception ex)
{
var exceptionString = ex.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString);
ExceptionVisualizer
.TestShowVisualizer(EnumerableExtensions.Reverse(exceptions));
}
}


public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}

internal class User
{
public User(string name) { }
}




Running



There is one more component that is required to run it in Visual Studio. It's the custom object-source for the exception serialization:



public class ExceptionVisualizerObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var exceptionString = target.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString).Reverse();
Serialize(outgoingData, exceptions);
}
}


This needs to be registered with:



[assembly: DebuggerVisualizer(
visualizer: typeof(ExceptionVisualizer),
visualizerObjectSource: typeof(ExceptionVisualizerObjectSource),
Target = typeof(Exception),
Description = "Exception Visualizer")]




So what do you think about the parsing of the excpetion string and the UI? It's my first WPF application for a long time so it's probably not the state of the art. Is there anything you would improve either in the back and or front end?





As always, you can also find it on my GitHub under Reusable.DebuggerVisualizers. The Console code is here.










share|improve this question




















  • 1




    Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
    – Xiaoy312
    Dec 3 at 23:11






  • 1




    @Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
    – t3chb0t
    Dec 4 at 7:27













up vote
5
down vote

favorite
1









up vote
5
down vote

favorite
1






1





I use Autofac a lot and for everything and when you make a mistake and forget to register a dependency etc. it'll tell you exaclty what's wrong. Although its exceptions are very helpful, the exception strings are at the same time hard to read because it's a large blob of text:




System.Exception: Blub ---> Autofac.Core.DependencyResolutionException: An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope ---> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'. (See inner exception for details.) ---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'.
at Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(IComponentContext context, IEnumerable`1 parameters)




To find the reason for this exception in such a string isn't easy. This is better done by some tool so I created. It reads the string for me and presents it in a more friendly way. I implemented it as a Debugger Visualizer.



Screenshot-1





DebuggerVisualizer



The ExceptionVisualizer is virtually a single function that shows the WPF window with the exception strings:



public class ExceptionVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var data = (IEnumerable<ExceptionInfo>)objectProvider.GetObject();

var window = new Window
{
Title = "Exception Visualizer",
Width = SystemParameters.WorkArea.Width * 0.4,
Height = SystemParameters.WorkArea.Height * 0.6,
Content = new DebuggerVisualizers.ExceptionControl
{
DataContext = new ExceptionControlModel
{
Exceptions = data
},
HorizontalAlignment = HorizontalAlignment.Stretch
},
WindowStartupLocation = WindowStartupLocation.CenterScreen
};

window.ShowDialog();
}

public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}


It receives a collection of ExceptionInfos that I create with these helpers that parse the string by removing the stack trace and extracting exception names and messages. I know I could use the Exception object to extract exception names and messages but I'm going to reuse this parser in another tools later for parsing and searching logs so I didn't want to have two solutions.



public class ExceptionParser
{
public static string RemoveStackStrace(string exceptionString)
{
// Stack-trace begins at the first 'at'
return Regex.Split(exceptionString, @"^s{3}at", RegexOptions.Multiline).First();
}

public static IEnumerable<ExceptionInfo> ParseExceptions(string exceptionString)
{
// Exceptions start with 'xException:' string and end either with '$' or '--->' if an inner exception follows.
return
Regex
.Matches(exceptionString, @"(?<exception>(^|w+)?Exception):s(?<message>(.|n)+?)(?=( --->|$))", RegexOptions.ExplicitCapture)
.Cast<Match>()
.Select(m => new ExceptionInfo { Name = m.Groups["exception"].Value, Message = m.Groups["message"].Value });
}
}

public static class EnumerableExtensions
{
public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source) => new Stack<T>(source);
}


The DTO is



[Serializable]
public class ExceptionInfo
{
public string Name { get; set; }

public string Message { get; set; }

public override string ToString()
{
return Name + Environment.NewLine + Message;
}
}




GUI



On the UI side there is simple a WPF.UserControl with a ListBox and two TextBoxes. The Close button closes the window and the Copy button copies the list to Clipboard and contains a small animation that lets the button shrink and grow back to its original size. In order to keep it in the middle I also animate the right Margin.



<UserControl x:Class="Reusable.Apps.ExceptionControl"
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:Reusable.Apps"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="#FF404040"
>
<UserControl.Resources>
<local:ExceptionControlModel x:Key="DesignViewModel" />
<Style TargetType="TextBlock" x:Key="NameStyle">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="DarkOrange"/>
<Setter Property="Margin" Value="0,10,0,0" />
</Style>
<Style TargetType="TextBlock" x:Key="MessageStyle">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Foreground" Value="WhiteSmoke"/>
</Style>
<Style x:Key="Theme" TargetType="{x:Type Control}">
<Setter Property="Background" Value="#FF404040"></Setter>
</Style>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Close"></CommandBinding>
</UserControl.CommandBindings>
<Grid >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Style="{StaticResource Theme}" Grid.Row="0" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Width" Value="{Binding (Grid.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border>
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
</Border>
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DockPanel Grid.Row="1" HorizontalAlignment="Right" >
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,5,10,5" />
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"></Setter>
<Setter Property="FontSize" Value="15"/>
</Style>
</DockPanel.Resources>
<Button
Content="Copy"
Command="{x:Static local:ExceptionControlModel.CopyCommand}"
CommandParameter="{Binding}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="90" Duration="0:0:0.25"/>
<DoubleAnimation Storyboard.TargetProperty="Width" From="90" To="100" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,10,5" To="0,5,15,5" Duration="0:0:0.25"/>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,15,5" To="0,5,10,5" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button
Content="Close"
Command="{x:Static local:ExceptionControlModel.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</DockPanel>
</Grid>
</UserControl>


and this is its model with some design-time data:



public class ExceptionControlModel
{
public static readonly ICommand CloseCommand = CommandFactory<Window>.Create(p => p.Close());

public static readonly ICommand CopyCommand = CommandFactory<ExceptionControlModel>.Create(p => p.CopyToClipboard());

public IEnumerable<ExceptionInfo> Exceptions { get; set; } = new
{
// This is design-time data.
new ExceptionInfo {Name = "DependencyResolutionException", Message = "An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope"},
new ExceptionInfo {Name = "DependencyResolutionException", Message = "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters: Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'."},
};

private void CopyToClipboard()
{
Clipboard.SetText(Exceptions.Join(Environment.NewLine + Environment.NewLine));
}
}


The command creation is supported with this helper factory that passes a strong type to the handler delegate:



public static class CommandFactory<T>
{
public static ICommand Create([NotNull] Action<T> execute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));

return new Command(parameter => execute((T)parameter));
}

public static ICommand Create([NotNull] Action<T> execute, [NotNull] Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));
if (canExecute == null) throw new ArgumentNullException(nameof(canExecute));

return new Command(parameter => execute((T)parameter), parameter => canExecute((T)parameter));
}

private class Command : ICommand
{
private readonly Action<object> _execute;

private readonly Predicate<object> _canExecute;

public Command(Action<object> execute) : this(execute, _ => true) { }

public Command(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

#region ICommand

public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter) => _canExecute(parameter);

[DebuggerStepThrough]
public void Execute(object parameter) => _execute(parameter);

#endregion
}
}




Example



I use the following code for testing where I try to resolve an instance of the User class which is missing a dependency:



internal class ExceptionVisualizerExperiment
{
public static void Run()
{
try
{
try
{
var builder = new ContainerBuilder();
builder.RegisterType<User>();
var container = builder.Build();
container.Resolve<User>();
throw new DivideByZeroException("Blub");
}
catch (Exception ex)
{
throw new Exception("Blub", ex);

}
}
catch (Exception ex)
{
var exceptionString = ex.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString);
ExceptionVisualizer
.TestShowVisualizer(EnumerableExtensions.Reverse(exceptions));
}
}


public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}

internal class User
{
public User(string name) { }
}




Running



There is one more component that is required to run it in Visual Studio. It's the custom object-source for the exception serialization:



public class ExceptionVisualizerObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var exceptionString = target.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString).Reverse();
Serialize(outgoingData, exceptions);
}
}


This needs to be registered with:



[assembly: DebuggerVisualizer(
visualizer: typeof(ExceptionVisualizer),
visualizerObjectSource: typeof(ExceptionVisualizerObjectSource),
Target = typeof(Exception),
Description = "Exception Visualizer")]




So what do you think about the parsing of the excpetion string and the UI? It's my first WPF application for a long time so it's probably not the state of the art. Is there anything you would improve either in the back and or front end?





As always, you can also find it on my GitHub under Reusable.DebuggerVisualizers. The Console code is here.










share|improve this question















I use Autofac a lot and for everything and when you make a mistake and forget to register a dependency etc. it'll tell you exaclty what's wrong. Although its exceptions are very helpful, the exception strings are at the same time hard to read because it's a large blob of text:




System.Exception: Blub ---> Autofac.Core.DependencyResolutionException: An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope ---> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'. (See inner exception for details.) ---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters:
Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'.
at Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(IComponentContext context, IEnumerable`1 parameters)




To find the reason for this exception in such a string isn't easy. This is better done by some tool so I created. It reads the string for me and presents it in a more friendly way. I implemented it as a Debugger Visualizer.



Screenshot-1





DebuggerVisualizer



The ExceptionVisualizer is virtually a single function that shows the WPF window with the exception strings:



public class ExceptionVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var data = (IEnumerable<ExceptionInfo>)objectProvider.GetObject();

var window = new Window
{
Title = "Exception Visualizer",
Width = SystemParameters.WorkArea.Width * 0.4,
Height = SystemParameters.WorkArea.Height * 0.6,
Content = new DebuggerVisualizers.ExceptionControl
{
DataContext = new ExceptionControlModel
{
Exceptions = data
},
HorizontalAlignment = HorizontalAlignment.Stretch
},
WindowStartupLocation = WindowStartupLocation.CenterScreen
};

window.ShowDialog();
}

public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}


It receives a collection of ExceptionInfos that I create with these helpers that parse the string by removing the stack trace and extracting exception names and messages. I know I could use the Exception object to extract exception names and messages but I'm going to reuse this parser in another tools later for parsing and searching logs so I didn't want to have two solutions.



public class ExceptionParser
{
public static string RemoveStackStrace(string exceptionString)
{
// Stack-trace begins at the first 'at'
return Regex.Split(exceptionString, @"^s{3}at", RegexOptions.Multiline).First();
}

public static IEnumerable<ExceptionInfo> ParseExceptions(string exceptionString)
{
// Exceptions start with 'xException:' string and end either with '$' or '--->' if an inner exception follows.
return
Regex
.Matches(exceptionString, @"(?<exception>(^|w+)?Exception):s(?<message>(.|n)+?)(?=( --->|$))", RegexOptions.ExplicitCapture)
.Cast<Match>()
.Select(m => new ExceptionInfo { Name = m.Groups["exception"].Value, Message = m.Groups["message"].Value });
}
}

public static class EnumerableExtensions
{
public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source) => new Stack<T>(source);
}


The DTO is



[Serializable]
public class ExceptionInfo
{
public string Name { get; set; }

public string Message { get; set; }

public override string ToString()
{
return Name + Environment.NewLine + Message;
}
}




GUI



On the UI side there is simple a WPF.UserControl with a ListBox and two TextBoxes. The Close button closes the window and the Copy button copies the list to Clipboard and contains a small animation that lets the button shrink and grow back to its original size. In order to keep it in the middle I also animate the right Margin.



<UserControl x:Class="Reusable.Apps.ExceptionControl"
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:Reusable.Apps"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="#FF404040"
>
<UserControl.Resources>
<local:ExceptionControlModel x:Key="DesignViewModel" />
<Style TargetType="TextBlock" x:Key="NameStyle">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="DarkOrange"/>
<Setter Property="Margin" Value="0,10,0,0" />
</Style>
<Style TargetType="TextBlock" x:Key="MessageStyle">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Foreground" Value="WhiteSmoke"/>
</Style>
<Style x:Key="Theme" TargetType="{x:Type Control}">
<Setter Property="Background" Value="#FF404040"></Setter>
</Style>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Close"></CommandBinding>
</UserControl.CommandBindings>
<Grid >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Style="{StaticResource Theme}" Grid.Row="0" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Width" Value="{Binding (Grid.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border>
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
</Border>
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DockPanel Grid.Row="1" HorizontalAlignment="Right" >
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="0,5,10,5" />
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"></Setter>
<Setter Property="FontSize" Value="15"/>
</Style>
</DockPanel.Resources>
<Button
Content="Copy"
Command="{x:Static local:ExceptionControlModel.CopyCommand}"
CommandParameter="{Binding}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="90" Duration="0:0:0.25"/>
<DoubleAnimation Storyboard.TargetProperty="Width" From="90" To="100" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,10,5" To="0,5,15,5" Duration="0:0:0.25"/>
<ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,5,15,5" To="0,5,10,5" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button
Content="Close"
Command="{x:Static local:ExceptionControlModel.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</DockPanel>
</Grid>
</UserControl>


and this is its model with some design-time data:



public class ExceptionControlModel
{
public static readonly ICommand CloseCommand = CommandFactory<Window>.Create(p => p.Close());

public static readonly ICommand CopyCommand = CommandFactory<ExceptionControlModel>.Create(p => p.CopyToClipboard());

public IEnumerable<ExceptionInfo> Exceptions { get; set; } = new
{
// This is design-time data.
new ExceptionInfo {Name = "DependencyResolutionException", Message = "An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = User (ReflectionActivator), Services = [UserQuery+User], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope"},
new ExceptionInfo {Name = "DependencyResolutionException", Message = "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'UserQuery+User' can be invoked with the available services and parameters: Cannot resolve parameter 'System.String name' of constructor 'Void .ctor(System.String)'."},
};

private void CopyToClipboard()
{
Clipboard.SetText(Exceptions.Join(Environment.NewLine + Environment.NewLine));
}
}


The command creation is supported with this helper factory that passes a strong type to the handler delegate:



public static class CommandFactory<T>
{
public static ICommand Create([NotNull] Action<T> execute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));

return new Command(parameter => execute((T)parameter));
}

public static ICommand Create([NotNull] Action<T> execute, [NotNull] Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));
if (canExecute == null) throw new ArgumentNullException(nameof(canExecute));

return new Command(parameter => execute((T)parameter), parameter => canExecute((T)parameter));
}

private class Command : ICommand
{
private readonly Action<object> _execute;

private readonly Predicate<object> _canExecute;

public Command(Action<object> execute) : this(execute, _ => true) { }

public Command(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

#region ICommand

public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter) => _canExecute(parameter);

[DebuggerStepThrough]
public void Execute(object parameter) => _execute(parameter);

#endregion
}
}




Example



I use the following code for testing where I try to resolve an instance of the User class which is missing a dependency:



internal class ExceptionVisualizerExperiment
{
public static void Run()
{
try
{
try
{
var builder = new ContainerBuilder();
builder.RegisterType<User>();
var container = builder.Build();
container.Resolve<User>();
throw new DivideByZeroException("Blub");
}
catch (Exception ex)
{
throw new Exception("Blub", ex);

}
}
catch (Exception ex)
{
var exceptionString = ex.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString);
ExceptionVisualizer
.TestShowVisualizer(EnumerableExtensions.Reverse(exceptions));
}
}


public static void TestShowVisualizer(object objectToVisualize)
{
var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(ExceptionVisualizer));
visualizerHost.ShowVisualizer();
}
}

internal class User
{
public User(string name) { }
}




Running



There is one more component that is required to run it in Visual Studio. It's the custom object-source for the exception serialization:



public class ExceptionVisualizerObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var exceptionString = target.ToString();
exceptionString = ExceptionParser.RemoveStackStrace(exceptionString);
var exceptions = ExceptionParser.ParseExceptions(exceptionString).Reverse();
Serialize(outgoingData, exceptions);
}
}


This needs to be registered with:



[assembly: DebuggerVisualizer(
visualizer: typeof(ExceptionVisualizer),
visualizerObjectSource: typeof(ExceptionVisualizerObjectSource),
Target = typeof(Exception),
Description = "Exception Visualizer")]




So what do you think about the parsing of the excpetion string and the UI? It's my first WPF application for a long time so it's probably not the state of the art. Is there anything you would improve either in the back and or front end?





As always, you can also find it on my GitHub under Reusable.DebuggerVisualizers. The Console code is here.







c# parsing wpf mvvm visual-studio






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 3 at 20:51

























asked Dec 3 at 18:05









t3chb0t

33.8k746110




33.8k746110








  • 1




    Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
    – Xiaoy312
    Dec 3 at 23:11






  • 1




    @Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
    – t3chb0t
    Dec 4 at 7:27














  • 1




    Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
    – Xiaoy312
    Dec 3 at 23:11






  • 1




    @Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
    – t3chb0t
    Dec 4 at 7:27








1




1




Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
– Xiaoy312
Dec 3 at 23:11




Is there any reason behind parsing exception by string instead of navigating the layers with .InnerException? Are they coming from a log file?
– Xiaoy312
Dec 3 at 23:11




1




1




@Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
– t3chb0t
Dec 4 at 7:27




@Xiaoy312 tl;dr? ;-) I wrote about it - I want to reuse this parser later for evaluating logs too so I didn't want to have two different solutions.
– t3chb0t
Dec 4 at 7:27















active

oldest

votes











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208946%2fvisual-studio-exception-visualizer-for-lengthy-exception-messages%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown






























active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208946%2fvisual-studio-exception-visualizer-for-lengthy-exception-messages%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Список кардиналов, возведённых папой римским Каликстом III

Deduzione

Mysql.sock missing - “Can't connect to local MySQL server through socket”