Обработка исключений ReactiveUI

Я просмотрел несколько образцов ReactiveUI, но я не вижу хорошего простого примера того, как обрабатывать исключения, где сообщение должно отображаться пользователю. (Если есть хороший пример, может кто-нибудь указать мне на это?).

Мой первый вопрос заключается в том, как обрабатывать исключение с помощью ReactiveCommand и ToProperty. Например, у меня есть следующий код:

public class MainWindowViewModel : ReactiveObject
{
    public ReactiveCommand CalculateTheAnswer { get; set; }

    public MainWindowViewModel()
    {
        CalculateTheAnswer = new ReactiveCommand();

        CalculateTheAnswer
            .SelectMany(_ => AnswerCalculator())
            .ToProperty(this, x => x.TheAnswer);

        CalculateTheAnswer.ThrownExceptions
            .Select(exception => MessageBox.Show(exception.Message));
    }

    private readonly ObservableAsPropertyHelper<int> _theAnswer;
    public int TheAnswer
    {
        get { return _theAnswer.Value; }
    }

    private static IObservable<int> AnswerCalculator()
    {
        var task = Task.Factory.StartNew(() =>
        {
            throw new ApplicationException("Unable to calculate answer, because I don't know what the question is");
            return 42;
        });

        return task.ToObservable();
    }
}

Я думаю, что я должен неправильно понимать ThrownExceptions, потому что это наблюдаемое не получает никаких элементов, когда я запускаю код выше. Что я делаю неправильно?

Мой второй вопрос: как мне это сделать с помощью MVVM. В этой записи в блоге упоминается функция Ошибки пользователя, но я не могу найти документацию о том, как ее использовать. Как реализовать его в приведенном выше примере?

Изменить. Я опубликовал примерное решение в github на основе ответа Павла ниже.

Ответ 1

Вы понимаете ThrownExceptions, но это не тот парень, _theAnswer.ThrownExceptions получит Исключение. Но сложная часть, теперь эта кнопка больше не работает - как только Observable заканчивает OnError, это делается навсегда.

В итоге вам нужно сделать несколько обратных ссылок, например:

static IObservable<int?> AnswerCalculator()

CalculateTheAnswer
    .SelectMany(_ => AnswerCalculator())
    .Catch(Observable.Return(null))
    .Where(x => x != null)
    .Select(x => x.Value)
    .ToProperty(this, x => x.TheAnswer);

В этом случае ReactiveAsyncCommand намного проще, так как для каждого вызова создается новый IObservable, поэтому вы должны:

// ReactiveAsyncCommand handles exceptions thrown for you
CalculateTheAnswer.RegisterAsyncTask(_ => AnswerCalculator())
    .ToProperty(this, x => x.TheAnswer);

CalculateTheAnswer.ThrownExceptions.Subscribe(ex => MessageBox.Show("Aieeeee"));

Как использовать UserError

Итак, UserError похоже на исключение, предназначенное для пользователя (т.е. содержит дружественный текст, а не текст программы)

Чтобы использовать UserError, вам нужно сделать две вещи: сначала измените ThrownExceptions:

CalculateTheAnswer.ThrownExceptions
    .SelectMany(ex => UserError.Throw("Something bad happened", ex))
    .Subscribe(result => /* Decide what to do here, either nothing or retry */);

И в вашем кодовом коде View, вызывайте `RegisterHandler ':

UserError.RegisterHandler(err => {
    MessageBox.Show(err.ErrorMessage);

    // This is what the ViewModel should do in response to the user decision
    return Observable.Return(RecoveryOptionResult.CancelOperation);
});

Классная часть состоит в том, что это позволяет проверить диалоги ошибок - в unit test:

var fixture = new MainWindowViewModel();
bool errorCalled;

using (UserError.OverrideHandlersForTesting(_ => { errorCalled = true; return RecoveryOptionResult.CancelOperation })) { 
    CalculateTheAnswer.Execute(null);
}

Assert.True(errorCalled);