How to show an unhandled exception window in WPF

In the previous post, we created a base class that listens for unhandled exceptions in our .NET application. In this post we will derive from that class to show an error window for every unhandled exception that occurs in our program.

The WindowExceptionHandler class

Let’s start with the WindowExceptionHandler. This class inherits from GlobalExcptionHanderBase (which we implemented in the previous post) and implements the OnUnhandledException(Exception) method. This method will be called whenever there is an unhandled exception in our application.

Inside that method, we create an instance of the ExceptionWindow class, set its DataContext property and display it by calling exceptionWindow.Show().

    /// <summary>
    /// This ExceptionHandler implementation opens a new
    /// error window for every unhandled exception that occurs.
    /// </summary>
    class WindowExceptionHandler : GlobalExceptionHandlerBase
    {
        /// <summary>
        /// This method opens a new ExceptionWindow with the
        /// passed exception object as datacontext.
        /// </summary>
        public override void OnUnhandledException(Exception e)
        {
            Application.Current.Dispatcher.BeginInvoke(new Action(() => {
                var exceptionWindow = new ExceptionWindow();
                exceptionWindow.DataContext = new ExceptionWindowVM(e);
                exceptionWindow.Show();
            }));
        } 
    }

Notice that the code is wrapped inside a Dispatcher.BeginInvoke(..) call. This is because the OnUnhandledException(..) method might be called from a thread other then the main thread. To make sure that our ExceptionWindow is created and shown on the main (dispatcher) thread we post our exception window creation with BeginInvoke(..) to be executed on the next dispatcher frame.

Creating the exception window

The content of an exception error window will be very different depending on your use case. If the window is for development purpose only, we probably want as much technical information about the exception as possible. If its presented to the non technical user, it might be better to just let them know that something went wrong without any technical details at all.

For the purpose of this post, we create a exception window that hides the details behind an expander control. The following screenshot shows the expanded window:

Screenshot of the expanded exception window

Here is the XAML code that creates the above window

<Window x:Class="Tcoc.ExceptionHandler.Windows.ExceptionWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Unexpected application error"
        Width="700"
        MaxHeight="650"
        ResizeMode="NoResize"
        SizeToContent="Height"
        mc:Ignorable="d">
    <Window.Resources>
        <Style x:Key="Label"
               TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Margin" Value="0,8,0,4" />
        </Style>
        <Style x:Key="Content"
               TargetType="TextBlock">
            <Setter Property="Margin" Value="0,4,0,8" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <!--  Info  -->
            <RowDefinition Height="auto" />
            <!--  Exception details  -->
            <RowDefinition Height="*" />
            <!--  Button  -->
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <!--  Info  -->
        <TextBlock Margin="16"
                   FontSize="14"
                   Text="An unexpected error occured. The application state might be corrupted."
                   TextWrapping="Wrap" />

        <!--  Exception details  -->
        <Border Grid.Row="1"
                MaxHeight="450"
                Background="#EEEEEE">
            <Expander Margin="16,8,16,8"
                      Header="Exception Details">

                <ScrollViewer HorizontalScrollBarVisibility="Auto"
                              VerticalScrollBarVisibility="Auto">
                    <StackPanel>
                        <!--  Exception Message  -->
                        <TextBlock Style="{StaticResource Label}"
                                   Text="Exception message" />
                        <TextBlock Grid.Column="1"
                                   Text="{Binding Exception.Message, Mode=OneWay, TargetNullValue=-}"
                                   TextWrapping="Wrap" />
                        <!--  Exception Type  -->
                        <TextBlock Style="{StaticResource Label}"
                                   Text="Exception type" />
                        <TextBlock Style="{StaticResource Content}"
                                   Text="{Binding ExceptionType, Mode=OneWay, TargetNullValue=-}" />
                        <!--  Callstack  -->
                        <TextBlock Style="{StaticResource Label}"
                                   Text="Callstack" />
                        <TextBlock Style="{StaticResource Content}"
                                   Text="{Binding Exception.StackTrace, Mode=OneWay, TargetNullValue=-}" />
                        <!--  Inner exception  -->
                        <TextBlock Style="{StaticResource Label}"
                                   Text="Inner exception" />
                        <TextBlock Style="{StaticResource Content}"
                                   Text="{Binding Exception.InnerException, Mode=OneWay, TargetNullValue=-}" />
                    </StackPanel>
                </ScrollViewer>
            </Expander>
        </Border>
        <!--  Close Button  -->
        <Button Grid.Row="2"
                Margin="16"
                Padding="8"
                HorizontalAlignment="Right"
                Click="OnExitAppClick"
                Content="Exit Application" />
    </Grid>
</Window>

To access the exceptions details via databinding the DataContext property of the window is set to an instance of the ExceptionWindowVM class.The view model holds the Exception object and its type as a string property:

    class ExceptionWindowVM
    {
        public Exception Exception { get; }

        public string ExceptionType { get; }

        public ExceptionWindowVM(Exception exc)
        {
            Exception = exc;
            ExceptionType = exc.GetType().FullName;
        }
    }

We could have used the Exception object itself as DataContext. But there is no Type property on the Exception class to bind to. There are of course other ways to display the type string but wrapping the exception in a view model makes the code easily extendable for future requirements.

To setup the WindowExceptionHandler in our application we just need to new up an instance of the class somewhere. This is typically done in a place where all the wiring of the application happens. In this sample project its simply the App’s constructor in App.xaml.cs:

    public partial class App : Application
    {
        private readonly WindowExceptionHandler _exceptionHandler;

        public App()
        {
            _exceptionHandler = new WindowExceptionHandler();
        }
    }

Thats all the code we need to show an exception window for every unhandled exception in our application!

Handle exception or exit application?

Once the exception windows shows up, there was an unhandled exception in our application. Which options do we give to the user now? Can she just close the exception window and use our application like before or is it better to shut down the application?

The application might work as expected even after an unhandled exception. But we have no way to proof this inside our global exception handler. Since the exception was not handled by our applications code, it might have corrupted the application state. Letting the user work with our application in this situation can be very dangerous. In the best case the app might work just like before, or there might be further exceptions. But it might also leave our application in a state where the user can do things she might not be allowed to or …, … the possibilities for error are endless.

In case of an unhandled exception in a real world application, we have no other choice then to shut down the application. This is why there is an “Exit application” button instead of an “OK” button on the exception window. There is also an event handler for the Closed event of the exception window to exit the app when the user presses the X on button.

A sample project

The MainWindow of the sample project for this post comes with a few buttons that throw different kind of exceptions. The simplest one is a normal exception that is thrown on the main thread. Then there is a button that produces an UnobservedTaskException on the finalizer thread and there are the bad ones that throw exceptions on worker threads.

As explained in the previous post, exceptions on threads other then the main thread cannot be handled and will terminate the process. This means our exception window wont work for these exceptions.

To make it work, we need to catch all exceptions on worker threads and re-throw them on to the main (dispatcher) thread. The code behind the last button “Throw on another Thread handled” does exactly that.

        private void RaiseExceptionOnThreadSafe(object sender, RoutedEventArgs e)
        {
            Thread t = new Thread(() =>
            {
                try
                {
                    // Some business logic 
                    // ...
                    throw new SampleException("Sample");
                }
                catch (Exception exc)
                {
                    // Catch all exceptions on the worker thread and
                    // re-throw them on the dispatcher to prevent an
                    // application crash without exception window.
                    exc.ThrowOnDispatcher();
                }
            });
            t.Start();
        }

It wraps the business logic inside a Try-Catch block and throws the exception on the dispatcher.

If you use the Task or Task<T> for asynchronous programming in your application, a lot of these problems are handled for you. Exceptions in async methods are catched by the framework and are stored in the returning Task object. Awaiting the task will then re-throw the exception on the calling thread. Unless you are using async void… In an async void method there is no Task object returned so the exception cannot be stored and will be thrown on whatever thread the method’s code is executed. This might not be the main thread. So do not create async void methods, but if you do, wrap the await part in a Try-Catch block and re-throw the exception on the dispatcher as above. Here is a handy ThrowOnDispatcher() extension method for this situation:

        public static void ThrowOnDispatcher(this Exception exc)
        {
            Application.Current.Dispatcher.BeginInvoke(new Action(() => {
                // preserve the callstack of the exception
                ExceptionDispatchInfo.Capture(exc).Throw();
            }));
        }

Summary

We used the GobalExceptionHandlerBase class from the previous post to create a WindowExceptionHandler that shows an exception window for every unhandled exception that occurs in our application.

We saw that it is important to shut down the application after an unhandled exception because we might have corrupted the application state and that exceptions on worker threads will terminate our application without a chance to display an error to the user.

All the code for this post can be found on this Github repository

Leave a Comment

Your email address will not be published. Required fields are marked *