如何从第二个API调用中返回相同的状态码。

3
我有一个ASP.NET Core API调用第二个API。
如果第二个API出现错误,我会在服务层中抛出异常:
var response = await httpClient.SendAsync(request); //call second API

if (!response.IsSuccessStatusCode)
{
    //return HTTP response with StatusCode = X, if response.StatusCode == X
    throw new HttpRequestException(await response.Content.ReadAsStringAsync()); 
    //this always returns 400
}

如何抛出一个异常,使得第二个 API 调用返回相同状态码的响应?
如果使用 HttpRequestException,即使 response 对象的 StatusCode 为 500,它也总是会返回 400。
编辑:第一个 API 端点如下:
            public async Task<ActionResult<HttpResponseMessage>> CreateTenancy([FromBody]TenancyRequest tenancy)
            {
                //Make some calls...
                return Created(string.Empty, new { TenancyID = newTenancyExternalId });
            }

第二个 API 终端的样子如下:
    [HttpPost]
    public IHttpActionResult CreateTenancy([FromBody]TenancyDTO tenancyDTO)
    {    
        var tenancy = GetTenancy();    
        return Created(string.Empty, tenancy);
    }

我尝试使用 throw new HttpResponseException(response);,但这会删除描述性异常消息,有效负载最终变成了这样:
{
    "Code": 500,
    "CorrelationId": "2df08016-e5e3-434a-9136-6824495ed907",
    "DateUtc": "2020-01-30T02:02:48.4428978Z",
    "ErrorMessage": "Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.",
    "ErrorType": "InternalServerError"
}

我想保留原始有效负载中的ErrorMessage值:
{
    "Code": 400,
    "CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
    "DateUtc": "2020-01-30T03:05:13.2397543Z",
    "ErrorMessage": "\"Specified cast is not valid.\"",
    "ErrorType": "BadRequest"
}

最终目标是将此返回:
{
    "Code": 500,
    "CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
    "DateUtc": "2020-01-30T03:05:13.2397543Z",
    "ErrorMessage": "\"Specified cast is not valid.\"",
    "ErrorType": "InternalServerError"
}

这个会起作用吗?抛出新的HttpRequestException(response)。 - Ctznkane525
不,构造函数不会接受它。 - David Klempfner
你尝试过使用 throw new HttpResponseException(response) 吗?即使用响应异常而不是请求异常? - Simply Ged
创建一个自定义的Exception,例如ApiException,它具有你关心的适当属性,包括一个StatusCode属性,然后 throw new ApiException(response.StatusCode)。如果你有一个全局的异常处理程序/中间件,你可以在那里捕获异常并设置Response.StatusCode - JohanP
@Jawad,请查看我的更新问题。 - David Klempfner
3个回答

2

我尝试了一些简单的事情,比如改变API端点的返回类型,并在发生错误时将对象作为返回值。否则,构建自己的HttpResponseMessage并返回。下面的代码片段使用文本,但如果您有其他内容需要序列化,可以使用序列化器。

public async Task<HttpResponseMessage> Test(string str)
{
    var httpClient = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Get, $"myAPI that returns different errors 400, 404, 500 etc based on str");

    var response = await httpClient.SendAsync(request);
    if (!response.IsSuccessStatusCode)
        return response;

    // do something else
    return new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("Your Text here") };
}

使用过滤器的另一种方法

如果您使用IHttpActionResult作为返回类型,您可以使用过滤器将所有HttpResponseMessages转换为IHttpActionResult。

过滤器: 创建一个单独的cs文件并使用此过滤器定义。

public class CustomObjectResponse : IHttpActionResult
{
    private readonly object _obj;

    public CustomObjectResponse(object obj)
    {
        _obj = obj;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = _obj as HttpResponseMessage;
        return Task.FromResult(response);
    }
}

在您的API中,您可以像这样使用您的过滤器:
public async Task<IHttpActionResult> Test(string str)
{
    var httpClient = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Get, $"http://localhost:4500/api/capacity/update-mnemonics/?mnemonic_to_update={str}");

    var response = await httpClient.SendAsync(request);
    if (!response.IsSuccessStatusCode)
        return new CustomObjectResponse(response);

    // Other Code here

    // Return Other objects 
    KeyValuePair<string, string> testClass = new KeyValuePair<string, string>("Sheldon", "Cooper" );
    return new OkWithObjectResult(testClass);

    // Or Return Standard HttpResponseMessage
    return Ok();

}

1
您可以直接调用API并将其响应代码复制到与IStatusCodeActionResult兼容的内容中。
另一种选择是抛出自定义异常。创建类似以下的内容:
public class ApiCallException : Exception
{
    public APiCallException(int statusCode, ...)
    {
        ApiStatusCode = statusCode;
    }

    int ApiStatusCode { get; }
    ...
}

并将 API 结果中的状态码复制过来,然后抛出异常。
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{   
    var content = await response.Content.ReadAsStringAsync();
    throw new ApiCallException(500, content); 
}

您可以在调用AddMvc时注册异常过滤器以处理结果。
services.AddMvc(options => options.Filters.Add<ExceptionFilter>());

其中ExceptionFilter可以是类似于以下内容:

public class ExceptionFilter : IExceptionFilter
{
    // ...

    public void OnException(ExceptionContext context)
    {
        if (context.Exception is ApiCallException ace)
        {
            var returnObject = CreateReturnObjectSomehow();
            context.Result = new ObjectResult(returnObject) { StatusCode = ace.StatusCode };
        }
        else
        {
            // do something else
        }
    }
}

0
感谢Jawad和Kit提供的优秀答案,帮助我找到了以下解决方案:
原来有一些中间件处理了异常:
    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception exception)
        {
            if (httpContext.Response.HasStarted) throw;

            var statusCode = ConvertExceptionToHttpStatusCode(exception);

            httpContext.Response.Clear();
            httpContext.Response.StatusCode = (int)statusCode;
            httpContext.Response.ContentType = "application/json";

            if (statusCode != HttpStatusCode.BadRequest)
            {
                _logger.Error(exception, "API Error");
            }

            await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new Error(statusCode, httpContext.Request.CorrelationId(), exception.Message, statusCode.ToString())));
        }
    }

Error类的样子如下:

    public class Error
    {
        public int Code { get; }
        public Guid? CorrelationId { get; }
        public DateTime DateUtc { get; }
        public string ErrorMessage { get; }
        public string ErrorType { get; }

        public Error(HttpStatusCode code, Guid? correlationId, string errorMessage, string errorType)
        {
            Code = (int)code;
            CorrelationId = correlationId;
            DateUtc = DateTime.UtcNow;
            ErrorMessage = errorMessage;
            ErrorType = errorType;
        }
    }

我创建了这个类:

public class ApiCallException : Exception
{
    public int StatusCode { get; }
    public override string Message { get; }
    public ApiCallException(int statusCode, string message)
    {
        StatusCode = statusCode;
        Message = message;
    }
}

然后我更新了我的原始代码,加入了这个:

                if (!response.IsSuccessStatusCode)
                {
                    throw new ApiCallException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
                }

函数 ConvertExceptionToHttpStatusCode 是自定义的吗?在 .net 文档中找不到。另一个 SO 问题 也在寻找它。 - Anand Sowmithiran
@AnandSowmithiran 对不起,我不确定。我甚至都记不得这个项目是什么了,那是很久以前的事情了。 - David Klempfner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接