应用程序架构是:MVVM [Views - > ViewModels] - >存储库 - > API。
API层可以抛出TokenExpiredException
异常,我们最终希望在UI层处理(通过显示消息框并重定向到登录)。
今天,我们的虚拟机与存储库进行交互,如下所示:
SomeCommand {
await _repo.DoSomethingAsync();
}
我的问题是找到一个好的模式来处理来自API层的这个例外。我可以想到3种方法:
1)以BaseViewModel
方法包装每个存储库调用,该方法负责捕获和处理此视图模型无关的异常。
SomeCommand {
await base.RepoRequest(() => _repo.DoSomethingAsync());
}
BaseViewModel
所在的地方:
RepoRequest(action) {
try { action() }
catch (TokenExpiredException) {
// show message box
// redirect
}
任何其他异常(例如验证错误)都将在VM中处理。我在这里看到的问题是,忘记使用这种模式太容易了。我可能会直接在某处调用存储库并错过处理异常。
2)每个VM都会捕获此异常
SomeCommand {
try { await _repo.DoSomethingAsync(); }
catch (InvalidUsernameException) { ... }
catch (TokenExpiredException) {
// show message box
// redirect
}
与1)没有什么不同,同样的问题,需要更多的代码重复。
3)使用事件聚合器将消息从API层发布到BaseViewModel
。
ApiRequest {
var response = await _httpClient.ExecuteAsync<..>(...);
if (response.ErrorId == "InvalidUsername")
throw new InvalidUsernameException();
else if (response.ErrorId == "TokenExpired")
EventAggregator.Publish(new TokenExpiredException());
}
和BaseViewModel
onMessage(TokenExpiredException e) {
// show message box
// redirect
}
这样做的好处是可以让所有虚拟机(但基站)无需接线。
缺点是我对在API层的所有2)使用事件聚合器1)犹豫不决。
我们正在使用mvvm-light,这意味着只为Messenger
(它的事件聚合器)在更深层中引用这些库。
是否有人建议如何干净地实施此功能?
答案 0 :(得分:1)
我必须弄清楚同样的问题,但是我正在使用WCF和城堡wcf设施,这对我有帮助,因为该设施已经有point of extension来拦截呼叫。所以我刚刚创建了我的自定义AbstractWcfPolicy
并拦截了我想要管理的所有异常。
有了相同的想法,您可以考虑使用基于castle dynamic proxy的代理类,这样您的调用将保持await _repo.DoSomethingAsync();
,但在木材下,您的ExceptionInterceptor将拦截所有异常并执行任何操作你想要的:
[Serializable]
public class Interceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Before target call");
try
{
invocation.Proceed();
}
catch(Exception)
{
Console.WriteLine("Target threw an exception!");
throw;
}
finally
{
Console.WriteLine("After target call");
}
}
}
然后你可以抛弃一些你可能想要抛出的异常,对于某些异常或全部,你也可以实现一个发布 - 订阅(全局消息代理,messenger in MVVM Light)机制,其中你推动异常,然后在你的应用程序的任何一点,你可以订阅这些错误并做一些事情(记录,以不引人注目的方式向用户显示错误等等。)