如何使用MVP将服务层消息/错误传达给更高层?

我目前正在从UI编写ASP.Net应用程序。 我正在实施一个MVP架构,因为我厌倦了Winforms,并希望能够更好地分离关注点。

因此,对于MVP,Presenter处理由View引发的事件。 这是我用来处理用户创建的一些代码:

public class CreateMemberPresenter { private ICreateMemberView view; private IMemberTasks tasks; public CreateMemberPresenter(ICreateMemberView view) : this(view, new StubMemberTasks()) { } public CreateMemberPresenter(ICreateMemberView view, IMemberTasks tasks) { this.view = view; this.tasks = tasks; HookupEventHandlersTo(view); } private void HookupEventHandlersTo(ICreateMemberView view) { view.CreateMember += delegate { CreateMember(); }; } private void CreateMember() { if (!view.IsValid) return; try { int newUserId; tasks.CreateMember(view.NewMember, out newUserId); view.NewUserCode = newUserId; view.Notify(new NotificationDTO() { Type = NotificationType.Success }); } catch(Exception e) { this.LogA().Message(string.Format("Error Creating User: {0}", e.Message)); view.Notify(new NotificationDTO() { Type = NotificationType.Failure, Message = "There was an error creating a new member" }); } } } 

我使用内置的.Netvalidation控件完成了主表单validation,但现在我需要validation数据是否足以满足服务层的标准。

假设以下服务层消息可以显示:

  • 电子邮件帐户已存在(失败)
  • 引用用户输入不存在(失败)
  • 密码长度超过数据存储允许的长度(失败)
  • 会员创建成功(成功)

我们还要说,UI无法预期的服务层中会有更多规则。

目前,如果事情没有按计划进行,我正在让服务层抛出exception。 这是一个足够的策略吗? 这段代码对你们有异味吗? 如果我写了这样的服务层,你会不得不写这样使用它的Presenters吗? 返回代码看起来太老了,bool只是没有足够的信息。


不是由OP编辑:合并后续评论,由OP发布为答案


Cheekysoft,我喜欢ServiceLayerException的概念。 我已经有一个全局exception模块用于我不期望的exception。 你觉得让所有这些自定义exception变得单调乏味吗? 我认为捕获基础Exception类有点臭,但不确定从那里取得进展。

tgmdbm,我喜欢在那里聪明地使用lambda表达式!


感谢Cheekysoft的后续行动。 因此,如果您不介意用户显示单独的页面(我主要是Web开发人员),如果不处理exception,我猜这将是策略。

但是,如果我想在用户提交导致错误的数据的同一视图中返回错误消息,那么我是否必须在Presenter中捕获exception?

以下是Presenter处理ServiceLayerException时CreateUserView的样子:

创建用户

对于这种错误,最好将它报告给同一个视图。

无论如何,我认为我们现在超出了原来问题的范围。 我会玩你发布的内容,如果我需要更多详细信息,我会发布一个新问题。

这对我来说听起来很合适。 exception是可取的,因为它们可以从服务层内的任何位置抛出到服务层的顶部,无论它在服务方法实现中嵌套有多深。 这样可以保持服务代码的清洁,因为您知道调用演示者将始终获得问题通知。

不要抓住exception

但是, 不要在演示者中捕获exception ,我知道它的诱惑因为它使代码更短,但是您需要捕获特定的exception以避免捕获系统级exception。

规划简单的exception层次结构

如果要以这种方式使用exception,则应为自己的exception类设计exception层次结构。 在最小值创建一个ServiceLayerException类,并在出现问题时在服务方法中抛出其中一个。 然后,如果您需要抛出一个exception,该exception应该/可以由演示者以不同方式处理,您可以抛出ServiceLayerException的特定子类:例如,AccountAlreadyExistsException。

然后,您的演示者可以选择这样做

 try { // call service etc. // handle success to view } catch (AccountAlreadyExistsException) { // set the message and some other unique data in the view } catch (ServiceLayerException) { // set the message in the view } // system exceptions, and unrecoverable exceptions are allowed to bubble // up the call stack so a general error can be shown to the user, rather // than showing the form again. 

在您自己的exception类中使用inheritance意味着您不需要在演示者中捕获多重exception – 如果需要,您可以 – 并且您最终不会意外地捕获无法处理的exception。 如果您的演示者已经位于调用堆栈的顶部,请添加一个catch(exception)块以使用不同的视图处理系统错误。

我总是试着将我的服务层视为一个单独的可分发库,并将其视为具体的exception。 然后由演示者/控制器/远程服务实现决定是否需要担心特定细节或仅将问题视为一般错误。

正如Cheekysoft建议的那样,我倾向于将所有主要exception移到ExceptionHandler中,并让这些exception冒出来。 ExceptionHandler将为exception类型呈现适当的视图。

但是,任何validationexception都应该在视图中处理,但通常这种逻辑对于应用程序的许多部分是通用的。 所以我喜欢这样的帮手

 public static class Try { public static List This( Action action ) { var errors = new List(); try { action(); } catch ( SpecificException e ) { errors.Add( "Something went 'orribly wrong" ); } catch ( ... ) // ... return errors; } } 

然后在致电您的服务时,请执行以下操作

 var errors = Try.This( () => { // call your service here tasks.CreateMember( ... ); } ); 

然后在错误是空的,你很高兴去。

您可以进一步使用自定义exception处理程序扩展它,它处理不常见的exception。

在回答后续问题时:

至于创建exception变得乏味,你有点习惯。 使用好的代码生成器或模板可以在大约5或10秒内创建具有最少手动编辑的exception类。

但是,在许多现实世界的应用程序中,error handling可能是工作的70%,因此它只是游戏的一部分。

正如tgmdbm建议的那样,在MVC / MVP应用程序中,我让所有不可处理的exception冒泡到顶部并被委派给ExceptionHandler的调度程序捕获。 我进行了设置,以便它使用ExceptionResolver查看配置文件,以选择适当的视图来显示用户。 Java的Spring MVC库做得非常好。 这是Spring MVC的Exception解析器配置文件的片段 – 它用于Java / Spring,但你会明白这一点。

这会占用演示者/控制器的大量exception处理。

     rescues/UserNotFound   rescues/databaseProblem   rescues/networkTimeout   rescues/validationError   rescues/environmentNotConfigured   rescues/messageRejected