Visual Studio exception visualizer for lengthy exception messages
up vote
5
down vote
favorite
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.
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 ExceptionInfo
s 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 TextBox
es. 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
add a comment |
up vote
5
down vote
favorite
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.
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 ExceptionInfo
s 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 TextBox
es. 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
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
add a comment |
up vote
5
down vote
favorite
up vote
5
down vote
favorite
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.
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 ExceptionInfo
s 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 TextBox
es. 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
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.
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 ExceptionInfo
s 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 TextBox
es. 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
c# parsing wpf mvvm visual-studio
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
add a comment |
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
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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