Обработка исключений 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 получите исключение. Но самое сложное, что теперь эта кнопка больше не работает - как только наблюдаемый заканчивается, это делается навсегда.

вам в конечном итоге придется сделать несколько сальто назад здесь, что-то вроде:

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 */);

и в вашем представлении код-за, вызовите "RegisterHandler":

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

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

классная часть заключается в том, что это делает диалоги ошибок тестируемыми - в модульном тесте:

var fixture = new MainWindowViewModel();
bool errorCalled;

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

Assert.True(errorCalled);