问题
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:
- Issue the request inside a try-catch block.
- If there was no exception then examine the response's statusCode.
- 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