在没有视图的情况下处理 ViewModel 中的异常(在 ReactiveUI 中)
Handling exceptions in a ViewModel without a View (in ReactiveUI)
我目前正在使用包含许多 views/viewmodels 的 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,这也可以节省网络流量。但是,我似乎无法通过任何方式检查 View 是否附加到 ViewModel。有什么想法吗?
为什么不能在您的视图模型中使用 WhenActivated 扩展来 'stop'/ 处理在处理视图时不应再使用的可观察对象?
我相信您总能从订阅中处理的交互中捕获异常,只需添加一个 OnError 处理程序。
如果您的视图实现 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
抛出异常。要解决这个问题,您可以在停用视图模型时停止 运行 操作。或者,您可以按照杰米的建议捕获异常:RefreshError.Handle(ex).Catch(ex => Observable.Empty<Exception>())
解决方案的关键是使用 WhenActivated
,由 ISupportsActivation
在 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)
));
}
}
这非常有效。
我目前正在使用包含许多 views/viewmodels 的 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,这也可以节省网络流量。但是,我似乎无法通过任何方式检查 View 是否附加到 ViewModel。有什么想法吗?
为什么不能在您的视图模型中使用 WhenActivated 扩展来 'stop'/ 处理在处理视图时不应再使用的可观察对象?
我相信您总能从订阅中处理的交互中捕获异常,只需添加一个 OnError 处理程序。
如果您的视图实现 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
抛出异常。要解决这个问题,您可以在停用视图模型时停止 运行 操作。或者,您可以按照杰米的建议捕获异常:RefreshError.Handle(ex).Catch(ex => Observable.Empty<Exception>())
解决方案的关键是使用 WhenActivated
,由 ISupportsActivation
在 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)
));
}
}
这非常有效。