【问题标题】:Exception Handling in webapi MVC.netwebapi MVC.net 中的异常处理
【发布时间】:2016-11-10 09:20:06
【问题描述】:

我有一个带有控制器和服务的 API,当调用控制器中的一个操作时,我必须应用验证,此验证需要检查数据库中的数据以验证其是否正确。

据我所知有两种处理方式

1-在调用“更新”之前验证以防止错误

public IActionResult UpdateCustomer(CustomerDto customer)
{
   if (!customerService.Validate(customer))
   {
       return Send400BadRequest();
   }
   customerService.Update(customer);

   return Send200Ok();
}

2- 在内部调用更新验证并抛出异常。

 public IActionResult UpdateCustomer(CustomerDto customer)
 {
    customerService.Update(customer);    
    return Send200Ok();
 }

在客户服务中

  public void Update(CustomerDto customer)
  {
     if (!Validate(customer)
        throws new CustomValidationException("Customer is not valid");
     //Apply update
  }

我们已经有一个 ActionFilter 来处理 CustomValidationException,所以它会返回一个 BadRequest。

1) 优点 +不要使用异常来使用正在运行的流程

缺点 -控制器更胖,对每种情况都有更多的决定,它将决定哪个是输出

2) 优点 +操作更原子,所有逻辑都在服务内部。 +更容易测试 +每次使用该方法都会得到验证。

缺点 - 使用异常来管理流程。

哪个是更好的解决方案?

我真的需要论据来捍卫其中一个。

【问题讨论】:

  • 我不会考虑增加 2 行代码使控制器 IMO 膨胀。
  • 对我来说不是臃肿与否,更多的是决策不应该在控制器中
  • @Balder,在点击操作之前使用另一个操作过滤器进行验证。横切关注点和单一责任原则。
  • 如果你有一个业务逻辑层和一个服务层,我更喜欢在业务逻辑层中保留所有的业务逻辑规则,包括业务逻辑验证,并使用服务层作为业务逻辑层的包装器。跨度>

标签: asp.net-mvc exception asp.net-web-api exception-handling controller


【解决方案1】:

如果您有一个业务逻辑层和一个服务层,我更愿意保留所有业务逻辑规则,包括业务逻辑业务逻辑层中的验证并使用服务层作为业务逻辑层的包装器并在中抛出Exception业务方法。

在决定是否对业务验证规则使用异常时,您可以考虑:

1) 您的业务方法最好是一个工作单元。他们应该完成一项完整的任务。所以最好它们也包含验证规则。这样您就可以跨不同的服务层重用这样的业务逻辑层,或者在相同的不同方法中使用相同的工作单元服务层。如果您在业务逻辑层抛出业务验证异常,您将不会面临忘记验证或错误使用另一个验证规则的风险,并且每个服务方法/操作将为您执行单个任务,并且将尽可能轻量级。

考虑一下您何时可能需要为某些客户端公开 WCF 服务,或者例如您是否可以在不使用 WebAPI 的情况下使用 ASP.NET MVC,或者您是否想在同一个 WebAPI 的另一个 Action 方法中使用相同的逻辑。

如果您将业务逻辑验证放在 Web API 控制器中,则在创建 WCF 服务方法或创建 MVC 操作或其他服务方法时,您可能会忘记应用验证,或者可能会在不同的规则中应用错误的验证新的服务层

2) 考虑到第一个好处,您能否从显示成功、失败或在输出中包含有关失败原因的适当信息的方法中返回有意义的值?

我认为对所有这些目标都使用 out put of method 是不合适的。方法输出就是方法输出,应该是这种业务应用中的数据。它不应该有时是状态,有时是数据或有时是消息。抛出异常可以解决这个问题。

【讨论】:

  • 您的回答非常好,而且很完整,我为此给了您赏金,但是我的问题更具概念性。实际上,我确实认为从业务逻辑层抛出异常更好,但我需要争论这个问题,这是我的问题的目标,因为我收到了关于我的同事的想法,比如“抛出异常导致性能不佳”或“抛出处理逻辑的异常是代码异味”,这就是为什么我真的需要论据来讨论这是最好的解决方案
  • 感谢您的反馈,我试图描述为什么最好在业务逻辑层中抛出异常。它不是控制应用程序的流程。它使业务逻辑更可重用和更可靠。它保证每个工作单元,始终以相同的方式工作。但是,如果您将业务验证放在一个操作方法中,您可能会忘记在另一个使用相同业务逻辑的操作方法中应用相同的业务验证。或者显然,如果您使用另一个服务层,您应该再次在新服务层中调用相同的业务验证。
  • @Manjar 3 年多后回到这篇文章,我只能确认答案是要走的路。虽然我分享的是基于我在 N 层架构方面的经验,但我可以确认这也是联合架构或领域驱动设计的方式。
【解决方案2】:

我反对这里的其他意见,并说第一种方法都清楚地说明了您的方法的意图是什么,如果您决定不返回 400 错误,在场景中实现它会更容易一些#1。

另外,关于异常的一些想法。异常应该是exceptional,表示代码中发生的意外事件。未通过验证检查的公司不是异常事件,它要么通过,要么不通过。将 User 对象传递给 ValidateCompany() 方法应该会引发异常。

Here 是关于异常抛出的类似主题的一个很好的答案。它使用一个简单的示例问题来确定在这种情况下何时应该抛出异常。

关于“更容易测试” - 我不知道如何。您的控制器将使用您选择的任何选项进行两次测试,一个有效公司和一个无效公司。您的服务将对您选择的任何选项进行两次测试,一个有效公司和一个无效公司(显然在这里简化了您的服务层)。在任何情况下,您都希望确保您的控制器操作和服务层都可以处理无效和有效的公司对象。

【讨论】:

    【解决方案3】:

    我更喜欢 2 :)

    因为我认为服务可能会从另一个节点调用,而不仅仅是 asp.net 控制器,所以如果所有验证逻辑都在像服务层这样的单层中处理,那对我来说会很好。

    【讨论】:

      【解决方案4】:

      我认为使用 httpresponse 消息处理异常比其他任何方法都好。至少你得到了正确的错误响应,并带有正确的 http 响应消息作为输出。

            public HttpResponseMessage TestException()
            {
              try
              {
                  //if your code works well
                  return Request.CreateResponse(HttpStatusCode.OK);
              }
              catch (Exception ex)
              {
      
                  return  Request.CreateErrorResponse(HttpStatusCode.ExpectationFailed, ex.ToString());
              }
            }
      

      【讨论】:

        【解决方案5】:

        我会这样做:

        public IActionResult UpdateCustomer(CustomerDto customer)
            {
            try
                {
                consumerService.Update (customer);
                }
            catch (CustomValidationException)
                {
                return Send400BadRequest ();
                }
        
            return Send200Ok ();
            }
        

        在您的客户服务中:

           public void Update(CustomerDto customer)     
               {
               if (!Validate(customer))
                   throw new CustomValidationException("Customer is not valid");
               }
        

        这样您的服务就有了验证逻辑。因此,您的服务的任何其他调用者也将在尝试更新之前验证他们的输入,因此将收到正确的错误消息。 您的控制器不会有任何验证码。此外,拥有 try-catch 块意味着您可以优雅地处理 Update 调用可能引发的任何其他错误。希望对您有所帮助。

        【讨论】:

          【解决方案6】:

          接收一个可验证的客户模型,将此模型传递给数据服务层,并执行异常 如果数据服务层失败,则在控制器中处理。

          客户模型

          实现IValidatableObject 以捕获与业务逻辑相关的错误。

          public class CustomerModel : IValidatableObject
          {
              public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
              {
                  // example validation
                  if ( Balance < 100 )
                      yield return new ValidationResult("Balance >= 100 req.", new [] { nameof(Balance) });
              }
              public string Name { get; set; }
              public double Balance { get; set; }
          }
          

          API 控制器

          在您面向公众的UpdateCustomer API 方法中接收CustomerModel,然后引用ModelState 以确定客户是否有效。

          public IActionResult UpdateCustomer(CustomerModel customer)
          {
              if(ModelState.IsValid) // model validation succeeded
              {
                  try
                  {
                      customerService.Update(customer);
                  }
                  catch (ServiceUpdateException svcEx)
                  {
                      // handled failure at the service layer
                      return Conflict();
                  }
                  catch (Exception ex)
                  {
                      // unhandled error occured
                      return InternalServerError(ex);
                  }
                  // update succeeded
                  return Ok();
              }
              // invalid model state
              return BadRequest();
          }
          

          服务(数据层)

          public void Update(Customer customer)
          {
               //Apply update
               try
               {
                   database.Update(customer);
               }
               catch (DataStorageException dbEx)
               {
                   throw new ServiceUpdateException(dbEx);
               }
               catch
               {
                   throw;//unknown exception
               }
          }
          

          【讨论】:

            【解决方案7】:

            我认为您应该在 中验证控制器和服务,但验证稍有不同的东西。尤其是如果一开始只是一个 api 变成了一个 api,通常会出现一个 mvc 站点和一个 wpf 管理界面。

            使用Validation Attributes 和模型绑定可以快速“一次性”检查数据。他们是否提供了 id 供客户更新?他们是否提交了相关实体的外键值?价格在 0 到 1,000,000 之间吗?如果您有一个网站,那么您还可以获得开箱即用的客户端验证。至关重要的是,根本不需要连接到数据库,因为数据“显然”是错误的。

            还需要在服务中进行验证,因为您最终可能会有 3 或 4 个应用程序调用此服务,并且您希望编写一次业务逻辑。一些验证,例如。引用外键只能在数据库中进行,但是抛出异常并没有错。您的 api 的使用者应该可以访问有效值的范围,网站的用户应该从下拉列表中进行选择等,所以这种情况应该是例外

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-12-21
              • 1970-01-01
              • 2021-08-06
              • 2014-06-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多