Best practice to handle content in the response after a Web API call results in an exception

安稳与你 提交于 2021-01-29 08:53:07

问题


I'm working on a Core 3.1 Web API and an MVC application that uses it. In the MVC app I have UserRepo set up containing methods that send requests to the API:

public class UserRepo : IUserRepo
{
    private readonly IHttpClientFactory _clientFactory;

    public UserRepo(IHttpClientFactory httpClientFactory)
    {
        _clientFactory = httpClientFactory;
    }

    public async Task<User> GetById(int Id)
    {
        // same code structure as Update ...
    }

    public async Task<User> Update(User user)
    {
        HttpClient client = _clientFactory.CreateClient("NamedClient");

        try
        {
            HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
            return await response.Content.ReadFromJsonAsync<User>();
        }
        catch (Exception ex)
        {
            throw;
        }

    }

    public async Task<User> Insert(User user)
    {
        // same code structure as Update ...
    }
}

The Update method never throws errors like 400, 404, etc, that come back from the API, resulting in silent errors. I found that to cause exceptions I need to call response.EnsureSuccessStatusCode();, which worked.

However, the exception doesn't contain what I need to find out what went wrong with the API call. If a 400 error occurs, an exception will be thrown saying that 400 error occurred, but not why it occurred. The why is returned to the response variable and it may look something like this due to validation I have implemented:

{
  "errors": {
    "FirstName": [
      "The FirstName field is required."
    ]
  },
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "|502d647b-4c7425oa321c8c7b."
}

Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.

One idea I had was to catch the exception and log it along with the response text every time before throwing it. Then when my app crashes I can go to the logs and read the message returned. The Update method would look like this:

public async Task<User> Update(User user)
{
    HttpClient client = _clientFactory.CreateClient("NamedClient");

    HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));

    try
    {
        response.EnsureSuccessStatusCode();
    }
    catch (Exception ex)
    {
        string errorMessage =  await response.Content.ReadAsStringAsync()
        _logger.LogError(ex, errorMessage);
        throw;
    }

    return await response.Content.ReadFromJsonAsync<User>();
}

Another thought that came would be maybe it's possible to add the message to the exception itself and see it when it's thrown? Would it make sense to add the message as an inner exception?


回答1:


EnsureSuccessStatusCode throws an HttpRequestException if the StatusCode is different than 2xx.

In order to gain the most information from the response, you have to retrieve it manually.

The general flow could be described in the following way:

  1. Issue the request inside a try-catch block.
  2. If there was no exception then examine the response's statusCode.
  3. If it is different than the expected one(s) then try to read the response's body

And log everything.

Step #1

HttpResponseMessage response = null;  
try
{
    response = await httpClient.PutAsync(...);
}
catch (InvalidOperationException ioEx)
{
    //The request message was already sent by the HttpClient instance, but failed due to some protocol violation
    HttpClient.CancelPendingRequests();
    
    //TODO: logging
}
catch (TaskCanceledException tcEX) 
{
    //The request was not completed due to either it's timed out or cancelled
    if(!tcEX.CancellationToken.IsCancellationRequested)
        HttpClient.CancelPendingRequests();
    
    //TODO: logging
}
catch (HttpRequestException hrEx)
{
    //The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation.
    
    //TODO: logging
}

Step #2

HttpStatusCodes[] validResponseCodes = new [] {
     HttpStatusCode.OK, 
     HttpStatusCode.Created,
     HttpStatusCode.NoContent,
};

if(!validResponseCodes.Contains(response?.StatusCode))
{
    //Step #3
}

Step #3

string errorResponse = await response.Content.ReadAsStringAsync();
//Try to parse it if you know the structure of the returned json/xml/whatever



回答2:


Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.

Generally, exception details are only logged, and not returned. This is because details may include personally identifiable information or technical details that could reveal potential security vulnerabilities. There is an error details RFC that is becoming more common, but even that should not have details like PII or a stack trace.

In the case of one API (the MVC endpoint) calling another API (the actual API), the MVC endpoint should return a code in the 5xx range. Either 500 or 502 would be acceptable here. All such errors should be logged on the server side along with their details.

Note that the default behavior is to return 500 if an exception is propagated, so keeping the throw; is all you really need to do. However, it's normal to do error logging in the "pipeline", e.g., middleware for ASP.NET Core or something like a globally-installed action filter for ASP.NET MVC. This is to ensure all errors are logged while avoiding repetition.



来源:https://stackoverflow.com/questions/65739004/best-practice-to-handle-content-in-the-response-after-a-web-api-call-results-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!