在Web API调用导致异常后处理响应中内容的最佳实践



我正在开发一个Core 3.1 Web API和一个使用它的MVC应用程序。在MVC应用程序中,我设置了UserRepo,其中包含向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 ...
}
}

Update方法从不抛出从API返回的错误,如400、404等,从而导致静默错误。我发现要引起异常,我需要调用response.EnsureSuccessStatusCode();,这很有效。

然而,该异常不包含我需要查找API调用出了什么问题的内容。如果发生400错误,将抛出一个异常,说明发生了400错误,但不说明发生错误的原因。为什么返回到response变量,由于我已经实现了验证,它可能看起来像这样:

{
"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."
}

有没有一种广泛使用的方法来处理API中产生错误后返回的响应?我想知道为什么会出现400错误,这样我就知道该修复什么了。我只是不知道什么是";右";处理这些响应消息的方法。

我的一个想法是,在每次抛出异常之前,捕捉异常并将其与响应文本一起记录。然后,当我的应用程序崩溃时,我可以转到日志并读取返回的消息。Update方法如下所示:

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>();
}

另一个想法是,也许可以将消息添加到异常本身,并在抛出时看到它?将消息添加为内部异常有意义吗?

是否有一种广泛使用的方法来处理API中产生错误后返回的响应?我想知道为什么会出现400错误,这样我就知道该修复什么了。我只是不知道什么是";右";处理这些响应消息的方法。

通常,只记录异常详细信息,而不返回。这是因为细节可能包括个人身份信息或可能揭示潜在安全漏洞的技术细节。有一个错误详细信息RFC变得越来越常见,但即使这样也不应该有PII或堆栈跟踪之类的详细信息。

在一个API(MVC端点)调用另一个API(实际的API)的情况下,MVC端点应该返回5xx范围内的代码。这里可以接受500或502。所有这样的错误都应该连同它们的详细信息一起记录在服务器端。

请注意,如果传播异常,默认行为是返回500,因此您真正需要做的就是保留throw;;管道";,例如用于ASP.NET Core的中间件或类似于用于ASP.NET MVC的全局安装的动作过滤器之类的东西。这是为了确保记录所有错误,同时避免重复。

如果StatusCode不同于2xx,则EnsureSuccessStatusCode抛出一个HttpRequestException

为了从响应中获得最多的信息,您必须手动检索它。

一般流程可以用以下方式描述:

  1. 在try-catch块内发出请求
  2. 如果没有异常,则检查响应的状态代码
  3. 如果它与预期的不同,则尝试读取响应的正文

并记录所有内容。

步骤#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
}

步骤#2

HttpStatusCodes[] validResponseCodes = new [] {
HttpStatusCode.OK, 
HttpStatusCode.Created,
HttpStatusCode.NoContent,
};
if(!validResponseCodes.Contains(response?.StatusCode))
{
//Step #3
}

步骤#3

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

最新更新