C# 在ASP.NET Core 2.0 Web API中返回有意义的HTTP响应

C# 在ASP.NET Core 2.0 Web API中返回有意义的HTTP响应,c#,api,asp.net-web-api,asp.net-core,asp.net-core-2.0,C#,Api,Asp.net Web Api,Asp.net Core,Asp.net Core 2.0,我觉得返回有意义的HTTP响应是一个好主意,但我正试图找到正确的方法来处理这个问题 在我的ASP.NET Core Web API应用程序中,端点(即API操作方法)只需接收请求,调用我的业务层以获得响应并返回响应 在业务层,我检查请求是否被授权。如果未经授权,我会抛出一个异常,表明异常类型,即未经授权的请求,但在这些情况下,我的API端点只返回一个HTTP500。我宁愿返回一个HTTP401 问题是如何将我的低级异常转移到HTTP状态代码 两个问题: 是否值得尝试捕获在应用程序较低级别发生的异

我觉得返回有意义的HTTP响应是一个好主意,但我正试图找到正确的方法来处理这个问题

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

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

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

两个问题:

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

  • 当然,有一大堆HTTP响应代码,不能正确处理请求并不一定意味着这是500个内部服务器错误。如果有人请求更新一个不存在的对象怎么办?这不在500的范围之内(不确定哪一个可以工作-我猜是4xx,但建议您在处理此问题时提供一个常见HTTP响应的方便列表)。我通常认为500是“哦,我们这边发生了一些事情,我们不知道是什么……”之类的东西。对于大多数其他情况,有一个更合适的答案

    话虽如此,我觉得当异常进一步向顶层移动时,最好将其概括化。实体框架抛出一些深奥的异常,数据访问层将其封装在一个稍微更通用的DataAccessException中(当然,原始的是内部的),存储库可能会更通用地处理它,当它到达控制器进行响应处理时,您应该只有少量的“实际”异常应该处理的例外情况。返回一些相当通用的内容,在服务器上记录嵌套的异常,然后从那里开始


    My 2cents

    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
    错误代码是正确的——这意味着您编写了错误的代码(请参阅我的第一点)。

    假设您的控制器扩展了Microsoft.AspNet.MVC.controller,那么您将继承一些执行您需要的操作的方法,如OK、Forbidden、BadRequest、,结果等等。。因此,在您上面描述的情况下,您可以执行以下操作

    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状态码和数据。

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

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

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


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

    您是说我应该检查用户是否在API级别而不是在业务层获得授权?@Sam根据提供的信息,我不能确定,但实际上您应该尝试在每一层进行验证。永远不要假设层之间的数据是有效的。@Sam通常情况下,越早阻止错误的请求进入请求管道(在每一层下传递得越远),效果越好。这意味着大多数例外情况不太可能首先发生。因为您想就问题的离题部分表达您的意见:)。。。我完全不同意-5xx的意思是“不是呼叫者的错误”。调用有些复杂的服务可能会失败的原因有很多,比如内存不足、外部调用超时、相关服务配置错误等等。因此,讨论5xx vs.4xx vs.error message作为响应是错误的。@Sam-我同意Alexei的观点,这个问题在这里是离题的。然而,如果你将问题改写为“如何”去做而不是“我应该”去做,这将是一个合理的问题。这就是我想要的。非常感谢。