在 .Net Core 2.2 中,将 HttpResponseMessage 作为 IActionResult 返回的正确方式是什么?

48

我正在创建一个API控制器,基于有效载荷将请求路由到另一个Http终点,此代码运行在.Net Core 2.2上。

  [Route("api/v1")]
  public class RoutesController : Controller
  {
      [HttpPost]
      [Route("routes")]
      public async Task<IActionResult> Routes([FromBody]JObject request)
      {
                 
        var httpClient = new HttpClient();

        // here based on request httpCLient will make `POST` or `GET` or `PUT` request
        // and returns `Task<HttpResponseMessage>`. Lets assume its making `GET` 
        // call

       Task<HttpResponseMessage> response = await
         httpClient.GetAsync(request["resource"]);
            
       /*  ??? what is the correct way to return response as `IActionResult`*/
      }        
  }

根据SO帖子,我可以做到这一点

根据 SO 帖子,我可以实现这个。
        return StatusCode((int)response.StatusCode, response);

但是我不确定将HttpResponseMessage作为ObjectResult发送是否正确。

我也想确保内容协商可以正常工作。

更新于2022年7月25日
已更新正确答案


在之前的Web API框架中,您无法像返回HttpResponseMessage对象那样返回。相反,您可以创建一个自定义的IActionResult(例如HttpResponseMessageResult),该结果将在ActionResult的ExecuteResultAsync方法中将状态码、标头和正文复制到httpContext.Response中。 - Kalten
有什么例子吗?后端的HTTP API可能会返回JSON结果或流。因此,HttpResponseMessage将把该信息作为HttpContent返回。 - LP13
你能解决这个问题吗? - rboy
我已经在下面发布了我的答案:https://dev59.com/QVQJ5IYBdhLWcg3wT0Ay#54187518 - LP13
4个回答

51
public class HttpResponseMessageResult : IActionResult
{
    private readonly HttpResponseMessage _responseMessage;

    public HttpResponseMessageResult(HttpResponseMessage responseMessage)
    {
        _responseMessage = responseMessage; // could add throw if null
    }

    public async Task ExecuteResultAsync(ActionContext context)
    {
        var response = context.HttpContext.Response;


        if (_responseMessage == null)
        {
            var message = "Response message cannot be null";

            throw new InvalidOperationException(message);
        }

        using (_responseMessage)
        {
            response.StatusCode = (int)_responseMessage.StatusCode;

            var responseFeature = context.HttpContext.Features.Get<IHttpResponseFeature>();
            if (responseFeature != null)
            {
                responseFeature.ReasonPhrase = _responseMessage.ReasonPhrase;
            }

            var responseHeaders = _responseMessage.Headers;

            // Ignore the Transfer-Encoding header if it is just "chunked".
            // We let the host decide about whether the response should be chunked or not.
            if (responseHeaders.TransferEncodingChunked == true &&
                responseHeaders.TransferEncoding.Count == 1)
            {
                responseHeaders.TransferEncoding.Clear();
            }

            foreach (var header in responseHeaders)
            {
                response.Headers.Append(header.Key, header.Value.ToArray());
            }

            if (_responseMessage.Content != null)
            {
                var contentHeaders = _responseMessage.Content.Headers;

                // Copy the response content headers only after ensuring they are complete.
                // We ask for Content-Length first because HttpContent lazily computes this
                // and only afterwards writes the value into the content headers.
                var unused = contentHeaders.ContentLength;

                foreach (var header in contentHeaders)
                {
                    response.Headers.Append(header.Key, header.Value.ToArray());
                }

                await _responseMessage.Content.CopyToAsync(response.Body);
            }
        }
    }

4
不确定为什么你得到了-1的评分,但这确实有效,除了在我的.NET Core 2.2项目中将Headers.Append更改为Headers.TryAdd - pvenky
1
@Thor88 使用 discard 标识符来消除未使用变量的警告,例如 var _ = contentHeaders.ContentLength; - zafar
这个方案支持我所有的场景。被接受的答案在回环响应方面存在问题。假设你有两个Web API,(A)和(B)。 (A)中的一个端点调用(B),然后(B)调用不同的端点,但也在(A)中。 (B)回复到(A)。这个解决方案可以实现这个功能,但被接受的答案不能。你可能会问为什么会有人这样做。多个应用程序的集成网关。我只需调用集成网关,无论数据来自何处,即使是来自调用者。如果运行时定义了集成源,则不需要特别制定回环集成。 - calingasan
这应该是正确的答案。与内容标题配合得很好。 - Byju
我是asp.net core的新手,如何使用它? - GuidoG
显示剩余6条评论

31

您可以创建一个自定义的IActionResult,它将包装传输逻辑。

public async Task<IActionResult> Routes([FromBody]JObject request)
{
    var httpClient = new HttpClient();

    HttpResponseMessage response = await httpClient.GetAsync("");

    // Here we ask the framework to dispose the response object a the end of the user resquest
    this.HttpContext.Response.RegisterForDispose(response);

    return new HttpResponseMessageResult(response);
}

public class HttpResponseMessageResult : IActionResult
{
    private readonly HttpResponseMessage _responseMessage;

    public HttpResponseMessageResult(HttpResponseMessage responseMessage)
    {
        _responseMessage = responseMessage; // could add throw if null
    }

    public async Task ExecuteResultAsync(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;

        foreach (var header in _responseMessage.Headers)
        {
            context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray()));
        }

        if(_responseMessage.Content != null)
        {
            using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
            {
                await stream.CopyToAsync(context.HttpContext.Response.Body);
                await context.HttpContext.Response.Body.FlushAsync();
            }
        }
    }
}

我正在查看 https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ResponseMessageResult.cs 和 https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ApiController.cs 的第523行。不确定那会不会起作用。基本上它将 HttpReponseMessage 作为 ObjectResultResponse 属性返回。我也会尝试你的方法。 - LP13
在webapishim中,它们使用自定义格式化程序 (https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs),您可能需要在某个地方进行注册。这是比我更完整的实现。 - Kalten
没错,因为它是开源的,所以你可以获取你需要的部分灵感,而不必考虑其他兼容层。 - Kalten
1
我使用了您创建IActionResult的方法,但是我在ExecuteResultAsync中使用了来自https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs的代码。请参见我的帖子。 - LP13
不适用于以下情况。假设您有两个Web API,(A)和(B)。 (A)中的一个端点调用(B),然后(B)调用(A)中的不同端点。 (B)向(A)回复。 (A)无法处理来自(B)的响应。 - calingasan
你能解释一下为什么吗?似乎与这个话题无关。 - Kalten

2

只需将响应包装在Ok()操作返回类型中:

return Ok(response) 

所以你的代码会看起来像这样:

[Route("api/v1")]
public class RoutesController : Controller
{
    [HttpPost]
    [Route("routes")]
    public async Task<IActionResult> Routes([FromBody]JObject request)
    {

      var httpClient = new HttpClient();
   
      Task<HttpResponseMessage> response = await httpClient.GetAsync(request["resource"]);

      return Ok(response);
    }        
}

更多信息请点击这里:https://learn.microsoft.com/zh-cn/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1


如果结果不是200怎么办? - Roman Badiornyi
@RomanBadiornyi听起来像是在获取响应后使用if语句的好方法。请查看链接,您可以返回NotFound() BadRequest()(404和400响应)等。更多信息请参见此处:https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase?view=aspnetcore-6.0#methods - Michael G

0

ASP.NET Core有返回对象RedirectResult,用于重定向调用者。


首先,您提供的链接实现了 IHttpActionResult 而不是 IActionResult。我知道 .net core 有相同的对象。但是重定向用于将用户重定向到特定的 URL。我的问题与 API 相关,而不是网站。您不会从 API 进行重定向。 - LP13
我错误地记得IHttpActionResult实现了IActionResult。在这种情况下,RedirectResult是你想要的。 - Richard Fuller
你如何重定向一个POST请求?或者在不向用户发送凭据的情况下管理已认证的请求? - Kalten
重定向结果(RedirectResult)不支持POST请求吗?而且我不确定你在第二个问题中的意思。 - Richard Fuller
有时您想要转发的资源需要凭据,而这些凭据不能发送给请求的用户。在这种情况下,您无法发送重定向响应,但可以充当代理服务器。另一种情况是目标资源位于用户无法访问的网络中。 - Kalten

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