Adam Plocher
Adam Plocher

Reputation: 14243

Handling uncaught exceptions from bound MVVM properties

I'm trying to handle all uncaught Exceptions in my WPF application.

I created a dummy project and changed the App.xaml.cs to look like this

public partial class App : Application
{ 
    public App()
    {
        this.Dispatcher.UnhandledException += (sender, args) =>
        {
            Console.WriteLine("Test1");
        };

        AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
        {
            Console.WriteLine("Test2");
        };

        Application.Current.DispatcherUnhandledException += (sender, args) =>
        {
            Console.WriteLine("Test3");
        };

        AppDomain.CurrentDomain.FirstChanceException += (sender, args) =>
        {
            Console.WriteLine("Test4");
        };
    }
}

I also have this ViewModel and property called TestProp which I have bound to a TextBox.Text in XAML:

public class TestViewModel
{
    private string testProp;

    public string TestProp
    {
        get => testProp;
        set
        {
            testProp = value;

            // RAISE EXCEPTION IN PROPERTY SETTER
            throw new Exception("Test Exception");
        }
    }
}

Changing that property in the View will throw the exception, however none of the events in App.xaml.cs will get triggered, except for the FirstChanceException.

The problem with FirstChanceException, is it will get raised, even if I handle the Exception in code.

I simply want all Unhandled Exceptions to get handled globally so I can log the error and display an error dialog box, without having to wrap every getter/setter with try/catch's.

Is this possible? Thank you

Upvotes: 2

Views: 422

Answers (1)

Marlonchosky
Marlonchosky

Reputation: 526

Yes, that is possible. You must implement the App.DispatcherUnhandledException event. Review this article for show you how to implement it.

=============================================

UPDATE 1: Yes, as @Maciek Świszczowski mentioned in the comment, you had already implemented it in your initial code, it was my mistake not to notice it.

The problem you have is that throwing exceptions within a class that is used as view model goes to the WPF validation system. This feature is known as "Validation on the Data Object", and the exception not goes to the App.DispatcherUnhandledException event, but goes within the WPF validation system.

One way to make exceptions thrown in Data Object notifications are handled globally in the App.DispatcherUnhandledException is to make them bubble up to the top of the application. In code, it could be accomplished by adding the ExceptionValidationRule class in the binding to the property, and throwing the exception inside the Validation.error attached event, something like this:

<Window
    x:Class="WpfApp10.MainWindow"
    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:local="clr-namespace:WpfApp10"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800" Height="450"
    d:DataContext="{d:DesignInstance Type=local:TestViewModel}"
    mc:Ignorable="d">
    <StackPanel>
        <TextBox Validation.Error="Validation_OnError">
            <TextBox.Text>
                <Binding NotifyOnValidationError="True" Path="TestProp" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <ExceptionValidationRule />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
    </StackPanel>
</Window>

and in the code behind:

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        var vm = new TestViewModel();
        this.DataContext = vm;
        //throw new Exception("Test2 Exception");
    }

    private void Validation_OnError(object sender, ValidationErrorEventArgs e) {
        throw e.Error.Exception;
    }
}

I tested the code here

Upvotes: 2

Related Questions