2017-07-06 33 views
1

我目前在C#中使用包含許多視圖/ viewmodels的ReactiveUI構建應用程序。其中一些視圖模型以預設間隔執行網絡請求。這些網絡請求可能隨時失敗。我實現了這個如下:在沒有視圖的ViewModel中處理異常(在ReactiveUI中)

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。有任何想法嗎?

回答

0

解決方案的關鍵是使用WhenActivated,由ViewModel上的ISupportsActivation提供。

我現在用在德視圖模型下面的代碼:

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) 
     )); 
    } 
} 

這完美的作品。

+0

嗨,剛剛出於好奇,爲什麼在處理程序中調用代碼'RefreshError.Handle(ex).Subscribe();'.Subscribe()?如果沒有它,它不會工作嗎? –

+1

.Handle()返回一個冷觀察值。冷觀察者需要使用。訂閱或等待被調用。 (研究這很迷惑,也許這應該在文檔中?) – Wouter

1

爲什麼不能在視圖模型中使用WhenActivated擴展來「停止」/處置不應該在視圖處置時不再播放的觀察對象?

我相信你總是可以從訂閱中處理的交互中捕捉異常,只需添加一個OnError處理程序即可。

+0

我一直在尋找WhenActivated,但認爲它不被支持,因爲ReactiveObject不提供它。但是,我只是發現了ISupportsActivation,它添加了WhenActivated並解決了問題。 :) – Wouter

2

如果你的視圖實現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 => Observable.Empty<Exception>())