【问题标题】:Correct use of EnsureSuccessStatusCode and IsSuccessStatusCode正确使用 EnsureSuccessStatusCode 和 IsSuccessStatusCode
【发布时间】:2018-01-26 03:25:48
【问题描述】:

我正在使用HttpClient 调用我的Web API,我看到有一个EnsureSuccessStatusCode 方法和一个IsSuccessStatusCode 属性。哪一个合适?

我阅读了这篇文章,还有几个问题:

Usage of EnsureSuccessStatusCode and handling of HttpRequestException it throws

我遇到的问题是,如果我发送一个 GET 请求并传递我要检索的对象的 ID,基本上有两种结果:

  1. 我以状态 200 取回对象
  2. 我可以返回 null,因为没有匹配项,但这会导致状态为 404。

如果我调用EnsureSuccessStatusCode(),状态 404 将导致抛出异常。这并不理想,因为当我测试我的代码时,我不断收到 404 错误,起初我认为 API URL 不正确,但实际上没有与提供的 Id 匹配的对象。在这种情况下,我宁愿返回一个空对象而不是抛出异常。

因此,我尝试检查 IsSuccessfulStatusCode 属性的值。这似乎是一个更好的选择,当此属性为 false 时,我可以返回一个 null 对象,但是,有许多状态代码会导致此属性具有 false 值。 404 是其中之一,但还有其他几个状态代码,例如 400 Bad Request405 Method Not Allowed 等。我想记录一个异常除了 404 之外的所有不成功的错误代码,我想知道是否有比检查响应的 ResponseCode 值然后抛出一个被我的 catch 块捕获的异常更好的方法,这是日志记录的地方发生。

这是我的 GET 方法的代码:

public static Customer GetCustomerByID(int id)
{
    try
        {
            using (var client = GetConfiguredClient())
            {
                Customer customer = null;
                var requestUri = $"Customers/{id}";

                using (var response = client.GetAsync(requestUri).Result)
                {
                    if (response.IsSuccessStatusCode)
                        customer = response.Content.ReadAsAsync<Customer>().Result;
                }

                return customer;
            }
        }
        catch (Exception ex)
        {
          ex.Data.Add(nameof(id), id);

          LogException(ex);

          throw;
        }
    }

如果返回不成功的状态代码并且没有记录任何内容,则此代码将仅返回 null Customer

处理这种情况的最佳方法是什么?

【问题讨论】:

    标签: c# asp.net-web-api exception-handling httpresponse


    【解决方案1】:

    accepted answer 采用“异常分支”,这被一些人认为是反模式。以下是如何使用 EnsureSuccessStatusCodeIsSuccessStatusCode 的方式,仅对意外错误或不能或不应“本地”处理的错误使用异常:

    1. 如果您想处理特定的错误响应,请直接使用 if-statements 进行处理。
    2. 如果您想将所有(剩余的)错误响应视为意外错误,请使用EnsureSuccessStatusCode 并且不要捕获异常,但假设它将由实际可以执行的捕获处理程序处理关于它的一些东西(例如更高级别的应用程序逻辑,或通用的顶级错误处理程序)。
    3. 如果您想对所有(剩余的)错误响应(例如日志记录)执行某些操作,然后正常进行,或者如果您想抛出自己的异常类型,请使用IsSuccessStatusCode

    这种方法为您提供了异常的所有优点,同时最大限度地减少了缺点(例如在您可能不感兴趣的完全正常的事件上中断调试器,或者在您的代码中使用难以阅读和写比 if 语句)。

    例子:

    using (var response = client.GetAsync(requestUri).Result)
    {
      if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
      {
        // TODO: Special handling for "401 Unauthorized" goes here
      }
      else
      {
        // All other unsuccessful error codes throw
        response.EnsureSuccessStatusCode();
    
        // TODO: Handling of successful response goes here
      }
    }
    

    ...或者如果您想阅读错误响应或进行日志记录等:

    using (var response = client.GetAsync(requestUri).Result)
    {
      if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
      {
        // TODO: Special handling for "401 Unauthorized" goes here
      }
      else if (!response.IsSuccessStatusCode)
      {
        // TODO: Read error response, logging, throw custom exception, etc., goes here
    
        // TODO: Keep this if you still want to throw the standard exception.
        // TODO: Otherwise, remove this.
        response.EnsureSuccessStatusCode();
      }
      else
      {
        // TODO: Handling of successful response goes here
      }
    }
    

    【讨论】:

      【解决方案2】:

      根据本文档:https://docs.microsoft.com/en-us/windows/uwp/networking/httpclient 可以解决以下问题:

      Uri requestUri = new Uri("http://www.contoso.com");
      
      //Send the GET request asynchronously and retrieve the response as a string.
      Windows.Web.Http.HttpResponseMessage httpResponse = new 
      Windows.Web.Http.HttpResponseMessage();
      string httpResponseBody = "";
      
      try
      {
          //Send the GET request
          httpResponse = await httpClient.GetAsync(requestUri);
          httpResponse.EnsureSuccessStatusCode();
          httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
      }
      catch (Exception ex)
      {
          httpResponseBody = "Error: " + ex.HResult.ToString("X") + " Message: " + ex.Message;
      }
      

      但是:httpResponse.EnsureSuccessStatusCode(); == 状态码从 200 到 299

      我正在使用 HttpStatusCode.OK = 200 = 是 EnsureSuccessStatusCode 的 SUB-PART,表示请求成功并且请求的信息在响应中。

      HttpResponseMessage result = await this._httpClient.GetAsync("https://www.web1.com/getThis", cancellationToken);
      if (result.StatusCode != HttpStatusCode.OK)
      {
          return new HttpResponseMessage(result.StatusCode)
          {
              Content = new StringContent( "ERROR DESCRIPTION HERE")
          };
      }
      return result;  // I can return here HttpResponseMessage....
      

      【讨论】:

      • 我不确定 httpResponse.EnsureSUccessStatusCode() 是否等于从 200 到 299 的状态码,实际上我发现如果状态码是 204 会抛出异常
      • 状态码信息 200-299 你可以在这里找到:docs.microsoft.com/en-us/uwp/api/…
      • 它说 204 表示“请求已成功处理,并且响应故意为空白。”,我认为这是一个成功请求。但是在 EnsureSuccessStatusCode 中它会抛出异常,这让我感到困惑。
      【解决方案3】:

      因为这个:

      所以我尝试检查 IsSuccessfulStatusCode 的值 财产。这似乎是一个更好的选择,我可以返回 null 此属性为 false 时的对象,但是,有许多状态 可能导致此属性具有错误值的代码。 404 是 其中之一,但还有其他几个状态码,例如 400 Bad 请求、405 方法不允许等。我想记录一个异常 除了 404 之外的所有不成功的错误代码,我想知道是否 有比检查ResponseCode 更好的方法来做到这一点 响应的值,然后抛出一个被捕获的异常 通过我的 catch 块,这是记录发生的地方。

      我会使用EnsureSuccessStatusCode 方法,然后修改catch 块,如下所示:

      public static Customer GetCustomerByID(int id)
      {
          try
          {
              using (var client = GetConfiguredClient())
              {
                  var requestUri = $"Customers/{id}";
                  Customer customer;
      
                  using (var response = client.GetAsync(requestUri).Result)
                  {
                      try 
                      {
                          response.EnsureSuccessStatusCode();
                          // If we reach here it means we can get the customer data.
                          customer = response.Content.ReadAsAsync<Customer>().Result;
                      }
                      catch(HttpRequestException)
                      {
                          if(response.StatusCode == HttpStatusCode.NotFound) // 404
                          {
                              customer = null;
                          }
                          else
                          {
                              throw;
                          }
                      }
                  }
      
                  return customer;
              }
          }
          catch (Exception ex)
          {
              ex.Data.Add(nameof(id), id);
      
              LogException(ex);
      
              throw;
          }
      }
      

      【讨论】:

      • 这种模式被称为异常分支,被一些人认为是不好的做法。
      • @ZachJohnson 不能这么糟糕。微软通过可以添加到 Catch 的“when”子句将这个确切的东西作为一项功能构建到 c#7 中。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
      • 我认为推断添加 when 子句并不意味着您的代码现在按异常分支。当您以抛出异常然后在 catch 语句中吞下它们的方式构建代码时,您就是按异常分支。有效地使用异常状态,如肯定的 if 语句。这不是“应该”使用 try/catch 语句的目的。 when 子句只是增加了您想要捕获的异常的粒度。
      • 我不喜欢EnsureSuccessStatusCode,因为它处理了response.Content 对象。错误响应可能包含有关错误的详细信息。如果您使用EnsureSuccessStatusCode,您将失去所有这些。剩下的就是异常块中的StatusCode,它只能说明部分情况。那是我的 2c。
      • +1 @onefootswill - 我今天遇到了一个问题,开发人员在阅读 response.Content 之前调用了 response.EnsureSuccessStatusCode();。就我而言,这隐藏了 Autofac 缺少类型注册的问题。我认为更大的问题是,当请求没有成功的状态码时,尝试读取response.Content 是否不安全或一个坏主意。
      猜你喜欢
      • 2014-02-01
      • 2017-08-13
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      • 2013-05-10
      • 2014-07-06
      • 2014-06-06
      • 2018-05-01
      相关资源
      最近更新 更多