.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
相关产品推荐
相关产品推荐

