如何在 .net Core MVC 控制器中返回 HttpResponseMessage 而不将其序列化为 JSON?

Posted

技术标签:

【中文标题】如何在 .net Core MVC 控制器中返回 HttpResponseMessage 而不将其序列化为 JSON?【英文标题】:How to return a HttpResponseMessage in a .net Core MVC Controller without Serializing it as JSON? 【发布时间】:2021-01-10 04:07:55 【问题描述】:

我正在研究一种使用 ASP.Net Core MVC 项目类型的 Web 代理模式。我想基本上将 HttpRequestMessage 传递给控制器​​中的 httpClient,然后向远程网站(如https://www.abc.fake)发出请求,然后完全按照从客户端返回的响应(正文和标头)返回响应。示例代码:

    [Microsoft.AspNetCore.Mvc.Route("[controller]")]
public class WeatherForecastController : ControllerBase

    //[HttpGet]
    public async Task<HttpResponseMessage> Get()
    
        var httpClient = new HttpClient();
        
         var resp = await httpClient.GetAsync("https://www.abc.fake");

        return resp;
        
    

问题是每次执行此操作时,我都会得到响应消息的 JSON 序列化版本。它实际上并没有发回我从远程网站获得的 html。这就是我所看到的:

"version":"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1,"content":"headers":["Key":"Content-Type","Value":["text/html; charset=utf-8"],"Key":"Content-Length","Value":["1036119"],"Key":"Expires","Value":["Wed, 23 Sep 2020 21:44:35 GMT"],"Key":"Last-Modified","Value":["Wed, 23 Sep 2020 21:44:35 GMT"]],"statusCode":200,"reasonPhrase":"OK","headers":["Key":"Connection","Value":["keep-alive"],"Key":"Vary","Value":["Accept-Encoding","Accept-Encoding"],"Key":"Date","Value":["Wed, 23 Sep 2020 21:41:54 GMT"],"Key":"Server","Value":["nginx/1.16.1"],"Key":"Via","Value":["1.1 varnish-v4","1.1 e8afb729a4bc6f5676d32307ea14bdae.cloudfront.fake (CloudFront)"],"Key":"Accept-Ranges","Value":["bytes"],"Key":"Cache-Control","Value":["must-revalidate, max-age=0"],"Key":"Set-Cookie","Value":["SWID=0C8B6C96-3F05-43D5-C3D1-2676E1C15F8C; path=/; Expires=Sun, 23 Sep 2040 21:41:54 GMT; domain=abc.fake;"],"Key":"X-Cache","Value":["Miss from cloudfront"],"Key":"X-Amz-Cf-Pop","Value":["HIO50-C1"],"Key":"X-Amz-Cf-Id","Value":["yKz-d9KhZdb-5qdDpppD0jeFqYHfFQA4Z1RT98Nk31eaH7kB_FXisQ=="]],"trailingHeaders":[],"requestMessage":"version":"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1,"content":null,"method":"method":"GET","requestUri":"https://abc.fake/","headers":["Key":"Request-Id","Value":["|8e9d36f9-4b9e69ca8ec31ee9.1."]],"properties":,"isSuccessStatusCode":true

【问题讨论】:

另外澄清我想从远程网站返回正文和标题。我基本上想从客户端返回整个 HttpResponseMessage。 你在用HttpResponseMessage 做什么来获得给你一个json字符串的响应?我想HttpResponseMessage.Content.ReadAsByteArrayAsync() 你会得到原始回复吗?然后您需要使用Encoding.GetString 来获取纯文本。而且它可能不会包含所有标题。 当前发生的是默认序列化行为是查看 HttpResponseMessage 并将其序列化为 JSON,然后将其发送回 MVC 控制器响应中。该行为只是序列化所有可读属性(如 http 标头),这不是我想要的行为。除了正文(即 Content.ReadAsByteArrayAsyn())之外,我实际上还想返回从远程调用中获得的所有标头。我基本上想把我从远程服务得到的响应交还给 MVC 服务的调用者(这是一个基本的代理模式)。 为什么不想使用自定义类而不是HttpResponseMessage?不确定它是否打算可序列化。也最好使用http client factory。 @tym32167 我不想使用自定义类的原因是因为我想将此模式用于未知类型的请求和响应。我试图将我的示例简化为单个网站,但我实际上想通过这种方法代理所有请求(想想 * 路由),它将它们发送到各种后端(基于 Host 标头),然后我需要完全按照从后端主机返回的响应发回。 【参考方案1】:

点击@tym32167 推荐给我的AspNetCore.Proxy nuget 包的链接后;我可以验证它是否符合我的要求。对于任何想要这样做的人来说,基本上就是这么简单:

[Route("*everything")]
public async Task Get()

    var host = Request.Host.Host;
    string path = Request.Path;
    string queryString = null;
    if (Request.QueryString != null)
    
        var queryStringBuilder = new StringBuilder();
        var isFirstParameter = true;
        foreach (var parameter in Request.Query)
        
            var leadingCharacter = isFirstParameter ? "?" : "&";
            queryStringBuilder.Append($"leadingCharacterparameter.Key=parameter.Value");
            isFirstParameter = false;
        
        queryString = queryStringBuilder.ToString();
    
    var requestUrl = $"Request.Scheme://hostpathqueryString";
    var b = HttpProxyOptionsBuilder.Instance.New();
    b.WithHttpClientName("default");
    b.WithShouldAddForwardedHeaders(true);
    var options = b.Build();         
    await this.HttpProxyAsync(requestUrl, options);

【讨论】:

您可能想改用QueryBuilder。也不确定是否是这个问题,但我一直担心手动构建查询 - 您可能遇到 URL 编码问题。【参考方案2】:

我参加这个聚会有点晚了,但值得一提的是,这种映射在早期用于现有 Web API 代码库过渡到 ASP.NET Core 之间的互操作非常常见。它已不再维护,但有一个由 ASP.NET 团队编写的 Web API Shim 库来支持转换。在该存储库中,您可以获取 2 个关键文件:

HttpResponseMessageOutputFormatter.cs ResponseMessageResult.cs

要连接它,你只需要像这样添加输出格式化程序:

mvcOptions.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());

使用它就像回到 Web API 模型中一样:

[Route("[controller]")]
public class WeatherForecastController : ControllerBase

    readonly IHttpClientFactory factory;

    public WeatherForecastController(IHttpClientFactory factory) => this.factory = factory;

    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken cancellationToken)
    
        using var client = factory.CreateClient("WeatherChannel");
        var response = await client.GetAsync("https://weather.com/", cancellationToken);
        return new ResponseMessageResult(response);
    

既然你提到你正在创建一个代理,你也可以从传入的 HttpRequest 构建一个 HttpRequestMesage,如果你需要将它转发到 HttpClient。代码有点复杂,但您可以从WebApiCompatShim 源获得所需的一切。

【讨论】:

【参考方案3】:

试试这样的

public async Task<IActionResult> Get()

     HttpClient httpClient = new HttpClient();
     var resp = await httpClient.GetAsync("https://www.abc.fake");
     if (!resp.IsSuccessStatusCode)
         return BadRequest();

     return resp.Content;
    

【讨论】:

我还想获取所有的 HTTP 响应标头。所以我希望有一个解决方案,我可以将完整的 HttpResponseMessage 从客户端调用返回到 MVC 控制器响应。如果远程服务器返回 HTTP 500、404、302 等。我想将其返回给调用者。我基本上想代理一切。 我建议您更新答案以使用await,而不是投反对票。您应该切勿async 方法中使用GetAwaiter().GetResult(),而不应等待 我不知道我为什么这样做,修复了代码

以上是关于如何在 .net Core MVC 控制器中返回 HttpResponseMessage 而不将其序列化为 JSON?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET Core 控制器中返回自定义 HTTP 响应?

如何使用 MVC 的内容协商在 ASP.NET Core MVC 中间件中返回响应?

如何在 Asp.net core mvc 的控制器中获取自己的用户

.Net Core Mvc 路由

如何在 ASP.NET Core MVC 中读取操作方法的属性?

如何在 .Net Core MVC 中发布