You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core代理控制器中如何将HttpResponseMessage转换为ActionResult?

刚好做过类似的.NET Core代理控制器需求,给你分享两种实用的实现方式,既能让调用方拿到下游服务器的完整响应细节,又能兼顾不同的业务场景:

两种实现方案

方案一:返回结构化的完整响应(JSON格式)

这个方案适合需要把下游响应包装成统一JSON格式的场景,调用方可以清晰获取状态码、响应头、响应体等所有细节。

完整代码示例

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System;

[ApiController]
[Route("api/proxy")]
public class ProxyController : ControllerBase
{
    private readonly HttpClient _httpClient;

    // 用IHttpClientFactory注入HttpClient(推荐方式,避免Socket资源泄漏)
    public ProxyController(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    [HttpPost("forward")]
    public async Task<IActionResult> ForwardRequest([FromBody] ProxyRequest request)
    {
        // 校验HTTP方法有效性
        if (!Enum.TryParse<HttpMethod>(request.HttpMethod, true, out var httpMethod))
        {
            return BadRequest(new { Error = "无效的HTTP方法,请输入GET/POST/PUT/DELETE等合法值" });
        }

        try
        {
            // 构建下游请求
            using var downstreamRequest = new HttpRequestMessage(httpMethod, request.Url);

            // 转发请求头(跳过不需要的系统头)
            foreach (var header in Request.Headers)
            {
                var skipHeaders = new[] { "Host", "Connection", "Content-Length", "Transfer-Encoding" };
                if (!skipHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase))
                {
                    downstreamRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                }
            }

            // 转发请求体(如果存在)
            if (Request.ContentLength > 0)
            {
                var requestBodyBytes = await Request.Content.ReadAsByteArrayAsync();
                downstreamRequest.Content = new ByteArrayContent(requestBodyBytes);
                // 复制Content-Type头,保证请求体格式正确
                downstreamRequest.Content.Headers.ContentType = Request.Content.Headers.ContentType;
            }

            // 发送请求到下游服务器
            using var downstreamResponse = await _httpClient.SendAsync(downstreamRequest, HttpCompletionOption.ResponseHeadersRead);

            // 包装完整响应信息
            var proxyResponse = new ProxyResponse
            {
                StatusCode = (int)downstreamResponse.StatusCode,
                ReasonPhrase = downstreamResponse.ReasonPhrase,
                ResponseHeaders = downstreamResponse.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()),
                ContentHeaders = downstreamResponse.Content.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()),
                Content = await downstreamResponse.Content.ReadAsStringAsync()
            };

            // 返回对应状态码的结构化响应
            return StatusCode((int)downstreamResponse.StatusCode, proxyResponse);
        }
        catch (HttpRequestException ex)
        {
            // 处理下游服务器不可达、连接超时等异常
            return StatusCode(StatusCodes.Status503ServiceUnavailable, 
                new { Error = "下游服务器请求失败", Details = ex.Message });
        }
        catch (Exception ex)
        {
            // 处理其他内部错误
            return StatusCode(StatusCodes.Status500InternalServerError, 
                new { Error = "代理服务器内部错误", Details = ex.Message });
        }
    }

    // 接收调用方请求的模型
    public class ProxyRequest
    {
        public string HttpMethod { get; set; }
        public string Url { get; set; }
    }

    // 返回给调用方的完整响应模型
    public class ProxyResponse
    {
        public int StatusCode { get; set; }
        public string ReasonPhrase { get; set; }
        public Dictionary<string, string[]> ResponseHeaders { get; set; }
        public Dictionary<string, string[]> ContentHeaders { get; set; }
        public string Content { get; set; }
    }
}

方案二:直接透传下游响应(保留原始格式)

如果你的代理需要完全透传下游的响应(比如返回HTML、图片、二进制文件等非JSON内容),可以用这种方式,直接把下游的响应原封不动地返回给调用方。

核心代码补充

在上面的控制器中,替换掉包装响应的部分,改用自定义的HttpResponseMessageResult来透传:

// 替换方案一中的包装响应代码
using var downstreamResponse = await _httpClient.SendAsync(downstreamRequest, HttpCompletionOption.ResponseHeadersRead);
// 直接返回下游响应
return new HttpResponseMessageResult(downstreamResponse);

然后添加自定义的IActionResult实现:

// 自定义Result用于透传HttpResponseMessage
public class HttpResponseMessageResult : IActionResult
{
    private readonly HttpResponseMessage _responseMessage;

    public HttpResponseMessageResult(HttpResponseMessage responseMessage)
    {
        _responseMessage = responseMessage;
    }

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

        // 设置状态码
        httpResponse.StatusCode = (int)_responseMessage.StatusCode;

        // 转发响应头
        foreach (var header in _responseMessage.Headers)
        {
            httpResponse.Headers.TryAdd(header.Key, header.Value.ToArray());
        }

        // 转发内容头
        foreach (var header in _responseMessage.Content.Headers)
        {
            httpResponse.Headers.TryAdd(header.Key, header.Value.ToArray());
        }

        // 复制响应体
        await using var responseStream = await _responseMessage.Content.ReadAsStreamAsync();
        await responseStream.CopyToAsync(httpResponse.Body);
        await httpResponse.Body.FlushAsync();
    }
}
关键注意事项
  • HttpClient的正确使用:一定要通过IHttpClientFactory注入HttpClient(在Program.cs中注册builder.Services.AddHttpClient();),不要在方法内手动new HttpClient,否则会导致Socket资源耗尽。
  • 请求头过滤:转发请求头时要跳过Host、Connection这类和当前请求绑定的系统头,避免出现请求冲突或错误。
  • 异常处理:代码中已经处理了下游不可达、内部错误等场景,同时会把下游返回的非2xx状态码(比如404、500)原封不动返回给调用方,让调用方能准确感知下游的状态。

内容的提问来源于stack exchange,提问作者Larry Lustig

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:11:21