【问题标题】:How Do You Communicate Service Layer Messages/Errors to Higher Layers Using MVP?您如何使用 MVP 将服务层消息/错误传达给更高层?
【发布时间】:2010-09-06 12:11:03
【问题描述】:

我目前正在从 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" });
        }
    }
}

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

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

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

我们也可以说,更多的规则将出现在 UI 无法预料的服务层中。

目前,如果事情没有按计划进行,我会让服务层抛出异常。这是一个足够的策略吗?这段代码对你们来说有味道吗?如果我写了一个这样的服务层,你会不会因为必须编写以这种方式使用它的 Presenter 而感到恼火?返回代码似乎太老套了,布尔值不够丰富。


非 OP 编辑​​:合并 OP 作为答案发布的后续 cmets


Cheekysoft,我喜欢 ServiceLayerException 的概念。对于我没有预料到的异常,我已经有了一个全局异常模块。您是否觉得让所有这些自定义异常变得乏味?我在想捕获基异常类有点臭,但不确定从那里进展如何。

tgmdbm,我喜欢那里巧妙地使用 lambda 表达式!


感谢 Cheekysoft 的跟进。因此,如果您不介意在未处理异常的情况下将用户显示为单独的页面(我主要是 Web 开发人员),我猜这将是策略。

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

这是 Presenter 处理 ServiceLayerException 后 CreateUserView 的样子:

对于这种错误,很高兴将其报告给同一个视图。

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

【问题讨论】:

    标签: c# asp.net exception mvp n-tier-architecture


    【解决方案1】:

    这听起来对我来说恰到好处。异常更可取,因为它们可以从服务层内部的任何地方抛出到服务层的顶部,无论它在服务方法实现中嵌套的深度如何。这样可以保持服务代码干净,因为您知道调用演示者将始终收到问题通知。

    不要捕捉异常

    然而,don't catch Exception 在演示者中,我知道它很诱人,因为它使代码更短,但您需要捕获特定异常以避免捕获系统级异常。

    规划一个简单的异常层次结构

    如果你打算以这种方式使用异常,你应该为你自己的异常类设计一个异常层次结构。 至少创建一个 ServiceLayerException 类,并在出现问题时在您的服务方法中抛出其中一个。然后,如果您需要抛出应该/可以由演示者以不同方式处理的异常,您可以抛出 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.
    

    在您自己的异常类中使用继承意味着您不需要在演示者中捕获多个异常(如果需要,您可以这样做),并且您最终不会意外捕获您无法处理的异常。如果您的演示者已经在调用堆栈的顶部,请添加一个 catch(Exception) 块以使用不同的视图来处理系统错误。

    我总是尝试将我的服务层视为一个单独的可分发库,并在合理的情况下抛出特定的异常。然后由presenter/controller/remote-service实现来决定是否需要担心具体的细节,或者只是将问题视为一般错误。

    【讨论】:

      【解决方案2】:

      正如 Cheekysoft 所建议的,我倾向于将所有主要异常移入 ExceptionHandler 并让这些异常冒泡。 ExceptionHandler 将为异常类型呈现适当的视图。

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

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

      然后在调用您的服务时,只需执行以下操作

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

      然后 in errors 是空的,你可以走了。

      您可以更进一步,并使用处理不常见异常的自定义异常处理程序对其进行扩展。

      【讨论】:

        【解决方案3】:

        回复后续问题:

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

        但是,在许多现实世界的应用程序中,错误处理可能占到工作的 70%,因此这只是游戏的一部分。

        正如 tgmdbm 建议的那样,在 MVC/MVP 应用程序中,我让所有无法处理的异常冒泡到顶部,并被委托给 ExceptionHandler 的调度程序捕获。我对其进行了设置,以便它使用在配置文件中查找的 ExceptionResolver 来选择适当的视图来向用户显示。 Java 的 Spring MVC 库在这方面做得很好。这是 Spring MVC 异常解析器的配置文件中的 sn-p - 它适用于 Java/Spring,但您会明白的。

        这完全需要您的演示者/控制器进行大量异常处理。

        <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>
        

        【讨论】:

          猜你喜欢
          • 2012-04-23
          • 1970-01-01
          • 2019-06-14
          • 1970-01-01
          • 2011-07-24
          • 1970-01-01
          • 2011-07-06
          • 1970-01-01
          • 2011-03-14
          相关资源
          最近更新 更多