针对 net461,HttpClient 在 GET 请求有内容时抛出 ex,但适用于 netstandard2.0

Targeting net461, HttpClient throws ex when GET request has content, but works with netstandard2.0

我需要使用第 3 方网站 api。
在这个特定端点上,我需要使用内容正文 (json) 发出 GET 请求。

var jsonPayload = JsonConvert.SerializeObject(myObject);

var request = new HttpRequestMessage(HttpMethod.Get, endpoint)
{
    Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
};

var client = new HttpClient();
var response = await client.SendAsync(request); //<-- explodes with net461,
//but works with netstandard2.0
var responseContent = await response.Content.ReadAsStringAsync();

我将该代码定位到 netstandard2.0,它起作用了。
但是,我现在需要在我以 net461 为目标的项目中使用,它会抛出异常“Cannot send a content-body with this verb-type

我知道设置内容GET请求是不常见的,但是api我做不到。

  1. 为什么当我定位 net461HttpClient.Send(request) 失败,但当我定位 netstandard2.0 时却运行良好?
  2. 当我定位 net461 时,我有哪些选择?

更新

一种复制方式。

ConsoleApp.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework> <!-- toggle this to net461 -->
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Net.Http" />
  </ItemGroup>

</Project>

Program.cs

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var jsonPayload = JsonConvert.SerializeObject(new { });
            var request = new HttpRequestMessage(HttpMethod.Get, "http://www.whosebug.com")
            {
                Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
            };
            var client = new HttpClient();
            var response = client.SendAsync(request).Result;
            var responseContent = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(responseContent);
        }
    }
}

dotnet --version 显示 2.1.104

你没有提到你 运行 它在哪个 .net 版本上工作(因为你可以针对网络标准但你不能 运行净标准)。所以我假设当它在 .NET Core 上工作时 运行,当它不工作时 运行 在完整的 .NET 4.6.1 上,如你所说。

那只是因为它在 .NET Core 和完整的 .NET Framework 中的实现方式不同,如本 issue 中所述。两种实现都没有错,因为 RFC 说:

A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.

因此,在 GET 中包含正文(虽然几乎从来不是一个好的做法)本身并不与 RFC 相矛盾。一种实现(较旧的)旨在拒绝它,但另一种(较新的)旨在允许它,仅此而已。

关于你的第二个问题"What options do I have when I target net461"。我认为没有简单的解决方法,因为那是 HttpWebRequest 的行为,而不是 HttpClient 的行为。几乎任何发出网络请求的东西都在内部使用 HttpWebRequest

虽然有丑陋的黑客使用反射。您的示例可以这样修改(仅当 运行ning 在完整的 .NET 上有效):

static void Main(string[] args) {     
    // UGLY HACK!       
    var verbType = typeof(HttpWebRequest).Assembly.GetType("System.Net.KnownHttpVerb");
    var getMethodField = verbType.GetField("Get", BindingFlags.Static | BindingFlags.NonPublic);
    var getMethod = getMethodField.GetValue(null);
    verbType.GetField("ContentBodyNotAllowed", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(getMethod, false);
    var jsonPayload = JsonConvert.SerializeObject(new { });
    var request = new HttpRequestMessage(HttpMethod.Get, "http://www.whosebug.com")
    {
        Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
    };
    var client = new HttpClient();
    var response = client.SendAsync(request).Result;
    var responseContent = response.Content.ReadAsStringAsync().Result;
    Console.WriteLine(responseContent);            
}

基本上我们使用反射将 KnownHttpVerb.Get 的名为 ContentBodyNotAllowed 的内部字段修改为 false。使用风险自负。