ReactiveUI异常处理

16

我查看了一些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,因为当我运行上面的代码时,这个observable没有接收到任何项。我做错了什么?
我的第二个问题是,我该如何以MVVM友好的方式实现这个功能。这篇博客文章提到了一个用户错误的功能,但我找不到任何关于如何使用它的文档。我该如何将其实现到上面的示例中? 编辑:我已经发布了一个示例解决方案在github上,基于Paul下面的答案。

嗨,韦恩,这太棒了!你介意将此示例贡献给 https://github.com/reactiveui/ReactiveUI.Samples (或者允许我这样做吗?) - Ana Betts
嗨,保罗,欢迎你将它添加到你的 GitHub 示例中。感谢你提供如此棒的框架。 - Wayne Maurer
2
我错了还是代码:CalculateTheAnswer.ThrownExceptions.Select(exception => MessageBox.Show(exception.Message));缺少实际的订阅? - kwesolowski
1个回答

34
你理解了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 */);

在你的视图代码后台中,调用"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);

1
感谢您提供的精彩答案。它就像一篇博客文章或文档一样好;-) - Wayne Maurer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接