在 .Net Core 2.2 中将 HttpResponseMessage 作为 IActionResult 返回的正确方法

Posted

技术标签:

【中文标题】在 .Net Core 2.2 中将 HttpResponseMessage 作为 IActionResult 返回的正确方法【英文标题】:Correct way to return HttpResponseMessage as IActionResult in .Net Core 2.2 【发布时间】:2019-06-05 18:48:17 【问题描述】:

在 .Net Core 2.2 中。我正在创建一个 API 控制器,该控制器根据有效负载将请求路由到另一个 Http 端点。

  [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 帖子我可以做到这一点

        return StatusCode((int)response.StatusCode, response);

但是我不确定将HttpResponseMessage 作为ObjectResult 发送是正确的方式。

我还想确保内容协商能够奏效。

【问题讨论】:

你不能像以前的 web api 框架那样返回一个 HttpResponseMessage 对象。相反,您可以创建一个自定义 IActionResult(如 HttpResponseMessageResult),它将状态码、标头和正文复制到 ActionResult 的 ExecuteResultAsync 方法中的 httpContext.Response 有什么例子吗?后端 http api 可能会返回 json 结果或流。所以HttpResponseMessage 的信息将是HttpContent 你能解决这个问题吗? 是的,我在***.com/a/54187518/3862378下方发布了我的答案 相关帖子 - Convert from HttpResponseMessage to IActionResult in .NET Core 【参考方案1】:

您可以创建一个自定义 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();
            
        
    

【讨论】:

我在第 523 行查看 github.com/aspnet/Mvc/blob/release/2.2/src/… 和 github.com/aspnet/Mvc/blob/release/2.2/src/…。不确定那会起作用。它基本上返回 HttpReponseMessage 作为 ObjectResult 的 Response 属性。我也试试你的方法 在 webapishim 中,他们使用您可能需要在某处注册的自定义格式化程序 (github.com/aspnet/Mvc/blob/release/2.2/src/…)。这是一个比我的更完整的实现。 是的,但由于它是开源的,您可以从所需的部分中获得灵感,而无需其他所有兼容性层。 我使用了您创建 IActionResult 的方法,但是我使用了 github.com/aspnet/Mvc/blob/release/2.2/src/… 中的代码 ExecuteResultAsync.. 请参阅下面的帖子 不适用于以下场景。假设您有 2 个 Web API,(A) 和 (B)。 (A) 中的端点调用 (B) 然后 (B) 调用不同的端点但也在 (A) 中。 (B) 回复 (A)。 (A) 无法处理来自 (B) 的响应【参考方案2】:
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);
            
        
    

【讨论】:

不知道为什么你得到了 -1,这确实有效,除了在我的 .net core 2.2 项目中将 Headers.Append 更改为 Headers.TryAdd 只是想添加一个评论,这在 .Net core 3.1 中有效,而不是接受的答案。 它适用于核心 3.1!如果您添加using Microsoft.AspNetCore.Http;,它将解析Headers.Append 扩展方法。 有更好的方法吗?不使用那个“未使用”的变量? @Thor88 使用丢弃标识符来消除未使用的变量警告,即var _ = contentHeaders.ContentLength;【参考方案3】:

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

【讨论】:

首先,您提供的链接实现了IHttpActionResult 而不是IActionResult。我知道 .net 核心有相同的对象。但是重定向用于将用户重定向到特定的 url。我的问题与 API 而非网站有关。您不会从 API 重定向 我记错了 IHttpActionResult 实现了 IActionResult。在这种情况下,RedirectResult 就是你想要的。 如何重定向 POST 请求?还是在不向用户发送凭据的情况下管理经过身份验证的请求? RedirectResult 不支持 POST 请求吗?我不确定你在第二个问题中的意思。 有时您要转发的资源需要您不能发送给请求用户的凭据。在这种情况下,您不能发送重定向响应,而是充当代理服务器。其他用例,目标资源位于用户无法访问的网络中。【参考方案4】:

只需将响应包装在 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://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1

【讨论】:

以上是关于在 .Net Core 2.2 中将 HttpResponseMessage 作为 IActionResult 返回的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Visual Studio 2017 中将 .NET Core 2.2 应用程序发布为依赖于框架的可执行文件 (FDE)

在 Razor (chtml) 中渲染动态视图,如何在 asp.net core 3.0 中将 FileProvider 添加到 razor?

如何在 Visual Studio 中将 .NET Framework 更改为 .NET Standard/Core?

从.Net core 2.2迁移到.Net Core 3.0

获取 ASP.NET-Core 2.2 控制器中的控制器名称和方法名称

在 .NET Core 中将应用设置与环境变量合并