【问题标题】:Handling exceptions in a ViewModel without a View (in ReactiveUI)在没有视图的视图模型中处理异常(在 ReactiveUI 中)
【发布时间】:2017-12-10 15:18:54
【问题描述】:

我目前正在使用包含许多视图/视图模型的 ReactiveUI 在 C# 中构建应用程序。其中一些视图模型以预设的时间间隔执行网络请求。这些网络请求可能随时失败。我是这样实现的:

public ReactiveCommand<Unit, IReactiveList<IJob>> RefreshJobList { get; }
public Interaction<Exception, Unit> RefreshError { get; } = new Interaction<Exception, Unit>();

...

RefreshJobList = ReactiveCommand.CreateFromTask(() => DoNetworkRequest());
RefreshJobList.ThrownExceptions.Subscribe(ex =>
{
    log.Error("Failed to retrieve job list from server.", ex);
    RefreshError.Handle(ex).Subscribe();
});
Observable.Interval(TimeSpan.FromMilliseconds(300)).Select(x => Unit.Default).InvokeCommand(RefreshJobList);

在对应的视图中,我对异常的处理如下:

this.WhenActivated(d => d(
    this.ViewModel.RefreshError.RegisterHandler(interaction =>
    {
        MessageBox.Show("Failed to load joblist.", "Error", MessageBoxButton.OK);
        interaction.SetOutput(new Unit());
    })
));

这很好用,除非视图模型与视图没有关联。我的应用程序使用选项卡,当用户切换到不同的选项卡时,之前的视图被破坏。这使得视图模型运行,仍在发出请求,没有视图。然后,当RefreshJobList 发生错误时,没有处理程序与RefreshError 关联,ReactiveUI 会抛出UnhandledInteractionError,我的应用程序崩溃。

我不确定如何干净地处理这个问题。我的第一个想法是暂停 ViewModel 直到附加视图,这也可以节省网络流量。但是,我似乎无法检查 View 是否附加到 ViewModel。有什么想法吗?

【问题讨论】:

    标签: c# wpf exception mvvm reactiveui


    【解决方案1】:

    如果您的视图实现了IViewFor,而您的视图模型实现了ISupportsActivation,您可以在离开视图时将订阅处理为ThrownExceptions

    // In your VM
    
    this.WhenActivated(d=>
    {
        RefreshJobList
            .ThrownExceptions
            .Do(ex => log.Error("Failed to retrieve job list from server.", ex))
            .SelectMany(ex => RefreshError.Handle(ex))
            .Subscribe()
            .DisposeWith(d); // This will dispose of the subscription when the view model is deactivated
    });
    

    如果在视图模型不活动时发生异常,这将使ReactiveCommand 抛出异常。为了解决这个问题,您可以在停用视图模型时停止正在运行的操作。或者,您可以按照 jamie 的建议捕获异常:RefreshError.Handle(ex).Catch(ex =&gt; Observable.Empty&lt;Exception&gt;())

    【讨论】:

      【解决方案2】:

      为什么您不能在视图模型中使用 WhenActivated 扩展来“停止”/处置在处置视图时不应再使用的可观察对象?

      我相信您总是可以从订阅中处理的交互中捕获异常,只需添加一个 OnError 处理程序。

      【讨论】:

      • 我一直在寻找WhenActivated,但假设它不受支持,因为 ReactiveObject 不提供它。但是,我刚刚发现 ISupportsActivation 添加了 WhenActivated 并解决了问题。 :)
      【解决方案3】:

      解决方案的关键是在 ViewModel 上使用 WhenActivated,由 ISupportsActivation 提供。

      我现在在 de viewmodel 中使用以下代码:

      public class ViewModel : ReactiveObject, ISupportsActivation
          public ViewModelActivator Activator { get; } = new ViewModelActivator();
      
          public ReactiveCommand<Unit, IReactiveList<IJob>> RefreshJobList { get; }
          public Interaction<Exception, Unit> RefreshError { get; } = new Interaction<Exception, Unit>();
      
          ...
      
          public ViewModel(){
              RefreshJobList = ReactiveCommand.CreateFromTask(() => DoNetworkRequest());
              RefreshJobList.ThrownExceptions.Subscribe(ex =>
              {
                  log.Error("Failed to retrieve job list from server.", ex);
                  RefreshError.Handle(ex).Subscribe();
              });
              this.WhenActivated(d => d(
                  Observable.Interval(TimeSpan.FromMilliseconds(300)).Select(x => Unit.Default).InvokeCommand(RefreshJobList)
              ));
          }
      }
      

      这很好用。

      【讨论】:

      • 嗨,出于好奇,为什么处理程序调用代码RefreshError.Handle(ex).Subscribe();中有.Subscribe()?没有它会不会一样工作?
      • .Handle() 返回一个冷的 observable。 Cold observables 需要与 .Subscribe 或 await 一起使用才能被调用。 (研究这个很混乱,也许这应该在文档中?)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多