【问题标题】:Returning meaningful HTTP responses in ASP.NET Core 2.0 Web API [closed]在 ASP.NET Core 2.0 Web API 中返回有意义的 HTTP 响应 [关闭]
【发布时间】:2017-10-07 17:41:50
【问题描述】:

我觉得返回有意义的 HTTP 响应是个好主意,但我正在尝试找到正确的方法来处理它。

在我的 ASP.NET Core Web API 应用程序中,端点(即 API 操作方法)只需接收请求,调用我的业务层以获得响应并返回响应。

在业务层,我检查请求是否被授权。如果它未经授权,我会抛出一个异常,指示异常类型,即未经授权的请求,但在这些情况下,我的 API 端点只返回一个 HTTP 500。我宁愿返回HTTP 401

问题是如何将我的低级别异常转移到HTTP 状态码。

两个问题:

  1. 是否值得尝试捕获应用程序中较低级别发生的异常类型并尝试将其转换为 HTTP 响应,还是应该让我的 API 返回一个 HTTP 500
  2. 如果值得,我该如何处理?

【问题讨论】:

  • SO 是讨论 5xx 与 4xx 与响应错误消息的错误地点。
  • @Sam - 我同意 Alexei 的观点,这个问题在这里是题外话。但是,如果您将问题改写为“如何”而不是“我应该”这样做,这将是一个合理的问题。

标签: c# api asp.net-web-api asp.net-core asp.net-core-2.0


【解决方案1】:

假设您的控制器扩展了 Microsoft.AspNet.MVC.Controller,您将继承一些执行您需要的方法,例如 OK、Forbidden、BadRequest、ObjectResult 等。因此,在您上面描述的情况下,您可以做类似的事情

public async IActionResult DoMyThing()
{
    try 
    {
        return ObjectResult(await DoMyInternalCall());
    }
    catch (Exception e) 
    {
        //FigureOut the Exception type indications a security violation
        return Forbidden() 
    }
    ....

就我个人而言,我更喜欢构建返回状态而不是抛出异常的 API,这使得这一切变得更加清晰,尤其是现在我们有了元组。所以像

public async IActionResult DoMyThing()
{
        var (Status status, string myThing) = await DoMyInternalCall();
        switch(status)
        {
             case Status.OK: return ObjectResult(myThing);
                             break;

             case Status.AccessDenied: return Forbidden();
                                       break;

             case Status.NotFound: return Notfound();

             ...

不过,这只是一种品味——我不评判。关键是,Microsoft.AspNet.MVC.Controller 内置了一些方法,让您可以将有效的 HTTP 状态代码与数据一起返回。

【讨论】:

  • 这就是我要找的。谢谢!
【解决方案2】:

我根本不喜欢这种抛出异常的想法。 500 表示您自己的代码确实有问题。

因此,假设您的业务层正在检查用户是否已获得授权。我要做的是创建一个检查授权的方法,例如让该方法返回一个简单的布尔响应。然后你的控制器检查标志,如果它是假的,返回 401,工作完成。这是在业务层和 api 层之间进行通信的一种更好的方式。

显然我无法知道您的业务层是如何构建的,但请保持简单,保持清晰,不要试图捕获任何异常,干净地处理所有内容并返回适当的 HTTP 代码。

业务层根本不应该关心 api,不应该处理 HTTP 代码,基本上这意味着您不会到处泄漏抽象,而是将事物保留在它们所属的层中。

【讨论】:

    【解决方案3】:

    Web API 应努力从不返回500 状态代码(内部服务器错误)。如果是这样,那么您编写的代码有问题。

    话虽如此,您不应该为了发回状态代码而抛出异常,这是处理请求的一种相当糟糕的方式——即通过全面捕获所有内容并用一些不错的状态代码来掩盖它客户。

    您应该对请求运行所有验证和逻辑,然后发回您选择的任何4xx 状态代码。

    本质上,对于 Web API,状态码应该是

    2XX -- Success //(ex: OK, created, no content, etc)
    3XX -- Redirection //(ex: renamed an API's path/url to a new one)
    4XX -- Client Error
           ex:
               405 //Method Not Allowed (ex: client sent a DELETE request to your API)
               409 //Conflict
               415 //Unsupported Media Type (ex: client requests for XML -- yuck! no!)
               416 //Range Not Suitable (ex: client asked for a million records)
               422 //Unpronounceable Entity (ex: client sent something invalid in the body)
    5XX -- Server Error //(ex: a well written Web API will NEVER error! )
    

    但是,如果您确实有异常,则 500 错误代码是正确的——这意味着您编写了错误的代码(请参阅我的第一点)。

    【讨论】:

    • 你是说我应该检查用户是否在 API 级别而不是在业务层获得授权?
    • @Sam 根据提供的信息我不能确定,但​​实际上您应该尝试在每一层进行验证。永远不要假设层之间的数据是有效的。
    • @Sam 通常,您可以越早阻止错误请求进入您的请求管道(进一步向下传递每一层)越好。这意味着大多数异常一开始不太可能发生。
    • 既然你想就问题的题外话表达你的意见:)......我完全不同意 - 5xx 只是意味着“一个不是调用者错的错误”。调用复杂服务失败的原因有很多——比如内存不足、外部调用超时、依赖服务配置错误……
    【解决方案4】:

    当然,有一大堆 HTTP 响应代码,不能正确处理请求并不一定意味着它是 500 内部服务器错误。如果有人请求更新一个不存在的对象怎么办?这不会落在 500 的范围内(不确定究竟哪个会起作用 - 我猜是 4xx,但是建议您在处理此问题时提供一个方便的常见 HTTP 响应列表)。我一般觉得 500s 是“呃,哦,我们这边发生了一些事情,我们不知道是什么......”类型的事情。对于大多数其他情况,有更合适的响应。

    话虽如此,我认为当异常进一步向顶层移动时,最好概括它们。实体框架抛出一些深奥的异常,数据访问层将其包装在一个稍微通用的 DataAccessException 中(当然,原始作为内部),存储库可能会更通用地处理它,并且当它到达您的控制器时对于响应处理,您应该只有少数应该需要处理的“实际”异常。返回相当通用的东西,在服务器上记录嵌套异常,然后从那里开始?

    我的两分钱

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-08
      • 2014-07-01
      • 1970-01-01
      • 2017-12-10
      • 2018-07-09
      • 1970-01-01
      • 2021-02-10
      相关资源
      最近更新 更多