C# 您如何使用 MVP 将服务层消息/错误传达给更高层?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21697/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How Do You Communicate Service Layer Messages/Errors to Higher Layers Using MVP?
提问by Scott Muc
I'm currently writing an ASP.Net app from the UI down. I'm implementing an MVP architecture because I'm sick of Winforms and wanted something that had a better separation of concerns.
我目前正在从 UI 向下编写 ASP.Net 应用程序。我正在实施 MVP 架构,因为我厌倦了 Winforms 并且想要一些可以更好地分离关注点的东西。
So with MVP, the Presenter handles events raised by the View. Here's some code that I have in place to deal with the creation of users:
因此,对于 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" });
}
}
}
I have my main form validation done using the built in .Net Validation Controls, but now I need to verify that the data sufficiently satisfies the criteria for the Service Layer.
我使用内置的 .Net 验证控件完成了我的主要表单验证,但现在我需要验证数据是否充分满足服务层的标准。
Let's say the following Service Layer messages can show up:
假设可以显示以下服务层消息:
- E-mail account already exists (failure)
- Refering user entered does not exist (failure)
- Password length exceeds datastore allowed length (failure)
- Member created successfully (success)
- 电子邮件帐户已存在(失败)
- 输入的引用用户不存在(失败)
- 密码长度超过数据存储允许的长度(失败)
- 成员创建成功(成功)
Let's also say that more rules will be in the service layer that the UI cannot anticipate.
我们还假设在服务层中会有更多 UI 无法预料的规则。
Currently I'm having the service layer throw an exception if things didn't go as planned. Is that a sufficent strategy? Does this code smell to you guys? If I wrote a service layer like this would you be annoyed at having to write Presenters that use it in this way? Return codes seem too old school and a bool is just not informative enough.
目前,如果事情没有按计划进行,我会让服务层抛出异常。这是一个足够的策略吗?这段代码对你们来说有味道吗?如果我写了一个这样的服务层,你会不会因为必须编写以这种方式使用它的 Presenters 而烦恼?返回代码似乎太老派了,布尔值的信息量不够。
Edit not by OP: merging in follow-up comments that were posted as answers by the OP
不由 OP 编辑:合并由 OP 作为答案发布的后续评论
Cheekysoft, I like the concept of a ServiceLayerException. I already have a global exception module for the exceptions that I don't anticipate. Do you find making all these custom exceptions tedious? I was thinking that catching base Exception class was a bit smelly but wasn't exactly sure how progress from there.
Cheekysoft,我喜欢 ServiceLayerException 的概念。我已经有一个针对我没有预料到的异常的全局异常模块。您是否觉得制作所有这些自定义异常很乏味?我在想捕获基础 Exception 类有点臭,但不确定从那里取得的进展。
tgmdbm, I like the clever use of the lambda expression there!
tgmdbm,我喜欢那里巧妙地使用 lambda 表达式!
Thanks Cheekysoft for the follow-up. So I'm guessing that would be the strategy if you don't mind the user being displayed a separate page (I'm primarily a web developer) if the Exception is not handled.
感谢 Cheekysoft 的跟进。所以我猜如果你不介意用户显示一个单独的页面(我主要是一个 web 开发人员),如果不处理异常,那将是策略。
However, if I want to return the error message in the same view where the user submitted the data that caused the error, I would then have to catch the Exception in the Presenter?
但是,如果我想在用户提交导致错误的数据的同一视图中返回错误消息,我将不得不在 Presenter 中捕获异常?
Here's what the CreateUserView looks like when the Presenter has handled the ServiceLayerException:
这是当 Presenter 处理 ServiceLayerException 时 CreateUserView 的样子:
For this kind of error, it's nice to report it to the same view.
对于这种错误,最好向同一个视图报告。
Anyways, I think we're going beyond the scope of my original question now. I'll play around with what you've posted and if I need further details I'll post a new question.
无论如何,我认为我们现在超出了我原来问题的范围。我会玩弄你发布的内容,如果我需要更多细节,我会发布一个新问题。
采纳答案by Cheekysoft
That sounds just right to me. Exceptions are preferable as they can be thrown up to the top of the service layer from anywhere inside the service layer, no matter how deeply nested inside the service method implementation it is. This keeps the service code clean as you know the calling presenter will always get notification of the problem.
这对我来说听起来恰到好处。异常是更可取的,因为它们可以从服务层内部的任何地方抛出到服务层的顶部,无论它在服务方法实现中嵌套多深。这使服务代码保持干净,因为您知道调用演示者将始终收到问题通知。
Don't catch Exception
不要捕捉异常
However, don't catch Exceptionin the presenter, I know its tempting because it keeps the code shorter, but you need to catch specific exceptions to avoid catching the system-level exceptions.
但是,不要在 Presenter 中捕获 Exception,我知道它很诱人,因为它使代码更短,但是您需要捕获特定的异常以避免捕获系统级异常。
Plan a Simple Exception Hierarchy
规划一个简单的异常层次结构
If you are going to use exceptions in this way, you should design an exception hierarchy for your own exception classes. At a minumum create a ServiceLayerException class and throw one of these in your service methods when a problem occurs. Then if you need to throw an exception that should/could be handled differently by the presenter, you can throw a specific subclass of ServiceLayerException: say, AccountAlreadyExistsException.
如果您打算以这种方式使用异常,您应该为您自己的异常类设计一个异常层次结构。至少创建一个 ServiceLayerException 类,并在出现问题时在您的服务方法中抛出其中一个。然后,如果您需要抛出一个应该/可以由演示者以不同方式处理的异常,您可以抛出 ServiceLayerException 的特定子类:例如 AccountAlreadyExistsException。
Your presenter then has the option of doing
然后你的演示者可以选择做
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.
Using inheritance in your own exception classes means you are not required to catch multipile exceptions in your presenter -- you can if there's a need to -- and you don't end up accidentally catching exceptions you can't handle. If your presenter is already at the top of the call stack, add a catch( Exception ) block to handle the system errors with a different view.
在您自己的异常类中使用继承意味着您不需要在演示器中捕获多个异常——如果需要,您可以这样做——并且您最终不会意外地捕获您无法处理的异常。如果您的演示者已经位于调用堆栈的顶部,请添加一个 catch( Exception ) 块以使用不同的视图处理系统错误。
I always try and think of my service layer as a seperate distributable library, and throw as specific an exception as makes sense. It is then up to the presenter/controller/remote-service implementation to decide if it needs to worry about the specific details or just to treat problems as a generic error.
我总是尝试将我的服务层视为一个单独的可分发库,并在有意义的情况下抛出一个特定的异常。然后由演示者/控制器/远程服务实现决定是否需要担心特定的细节或只是将问题视为一般错误。
回答by tgmdbm
As Cheekysoft suggests, I would tend to move all major exceptions into an ExceptionHandler and let those exceptions bubble up. The ExceptionHandler would render the appropriate view for the type of exception.
正如 Cheekysoft 所建议的那样,我倾向于将所有主要异常移动到 ExceptionHandler 中,并让这些异常冒泡。ExceptionHandler 将为异常类型呈现适当的视图。
Any validation exceptions however should be handled in the view but typically this logic is common to many parts of your application. So I like to have a helper like this
然而,任何验证异常都应在视图中处理,但通常此逻辑对应用程序的许多部分是通用的。所以我喜欢有这样的帮手
public static class Try {
public static List<string> This( Action action ) {
var errors = new List<string>();
try {
action();
}
catch ( SpecificException e ) {
errors.Add( "Something went 'orribly wrong" );
}
catch ( ... )
// ...
return errors;
}
}
Then when calling your service just do the following
然后在调用您的服务时只需执行以下操作
var errors = Try.This( () => {
// call your service here
tasks.CreateMember( ... );
} );
Then in errors is empty, you're good to go.
然后在错误是空的,你很高兴去。
You can take this further and extend it with custome exception handlers which handle uncommonexceptions.
您可以更进一步,并使用处理不常见异常的自定义异常处理程序对其进行扩展。
回答by Cheekysoft
In reply to the follow-up question:
回复后续问题:
As for creating exceptions becoming tedious, you kinda get used to it. Use of a good code generator or template can create the exception class with minimal hand editing within about 5 or 10 seconds.
至于创建异常变得乏味,你有点习惯了。使用好的代码生成器或模板可以在大约 5 或 10 秒内以最少的手动编辑创建异常类。
However, in many real world applications, error handling can be 70% of the work, so it's all just part of the game really.
然而,在许多现实世界的应用程序中,错误处理可能占工作的 70%,所以这只是游戏的一部分。
As tgmdbm suggests, in MVC/MVP applications I let all my unhandlable exceptions bubble up to the top and get caught by the dispatcher which delegates to an ExceptionHandler. I set it up so that it uses an ExceptionResolver that looks in the config file to choose an appropriate view to show the user. Java's Spring MVC library does this very well. Here's a snippet from a config file for Spring MVC's Exception resolver - its for Java/Spring but you'll get the idea.
正如 tgmdbm 所建议的那样,在 MVC/MVP 应用程序中,我让所有无法处理的异常冒泡到顶部并被委托给 ExceptionHandler 的调度程序捕获。我对其进行了设置,以便它使用 ExceptionResolver 在配置文件中查找以选择合适的视图来向用户显示。Java 的 Spring MVC 库在这方面做得很好。这是 Spring MVC 异常解析器的配置文件中的一个片段 - 它适用于 Java/Spring,但您会明白的。
This takes a huge amount of exception handling out of your presenters/controllers altogether.
这需要从演示者/控制器中处理大量异常。
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="UserNotFoundException">
rescues/UserNotFound
</prop>
<prop key="HibernateJdbcException">
rescues/databaseProblem
</prop>
<prop key="java.net.ConnectException">
rescues/networkTimeout
</prop>
<prop key="ValidationException">
rescues/validationError
</prop>
<prop key="EnvironmentNotConfiguredException">
rescues/environmentNotConfigured
</prop>
<prop key="MessageRejectedPleaseRetryException">
rescues/messageRejected
</prop>
</props>
</property>
<property name="defaultErrorView" value="rescues/general" />
</bean>