如何使用HttpClient在GET请求的内容中发送主体?

68

目前,为了向一个API接口发送带参数的GET请求,我正在编写以下代码:

api/master/city/filter?cityid=1&citycode='ny'

但我发现URL长度有2083个字符的限制。

为了避免这种情况,我希望用json格式发送参数到GET请求的内容体中。

然而,我发现HttpClient的所有Get方法都不允许发送内容体。对于POST方法,我可以看到HttpClient中有一个名为PostAsync的方法可以发送内容体。

是否存在一种方式,可以在不使用URL的情况下发送GET请求的参数,以避免URL长度的限制?


1
看看这个:https://dev59.com/53NA5IYBdhLWcg3wZ85S基本上,即使你在理论上可能做到这一点,你真的不应该这样做。 - Shivanshu Goyal
你不能使用HttpClient或WebClient或其他任何方式发送带有请求体的GET请求。即使你在低级别上设法这样做,服务器也不会解析请求体,因为它将把它视为GET请求。 - Selman Genç
你不能这样做的原因是根据定义,GET 方法旨在检索资源。没有人需要那么多字符来检索资源。更有可能的是您想要提交数据而不是检索数据,这是 POST 方法的设计初衷。参考链接:https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html - Chad Hedgcock
可以使用GET方法,例如getbyWhere?id=&id=.... 但是它会达到4K限制,将其改为POST方法也不太好。 - user1496062
2
@SelmanGenç 错了,请看我的回答。 - Ian Kemp
5个回答

113
请阅读本答案末尾的注释,了解为什么通常不建议使用带有请求体的HTTP GET请求。
  • If you are using .NET Core, the standard HttpClient can do this out-of-the-box. For example, to send a GET request with a JSON body:

      HttpClient client = ...
    
      ...
    
      var request = new HttpRequestMessage
      {
          Method = HttpMethod.Get,
          RequestUri = new Uri("some url"),
          Content = new StringContent("some json", Encoding.UTF8, MediaTypeNames.Application.Json /* or "application/json" in older versions */),
      };
    
      var response = await client.SendAsync(request).ConfigureAwait(false);
      response.EnsureSuccessStatusCode();
    
      var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    
  • .NET Framework doesn't support this out-of-the-box (you will receive a ProtocolViolationException if you try the above code). Thankfully Microsoft has provided the System.Net.Http.WinHttpHandler package that does support the functionality - simply install and use it instead of the default HttpClientHandler when constructing your HttpClient instances:

      var handler = new WinHttpHandler();
      var client = new HttpClient(handler);
    
      <rest of code as above>
    

    Reference: https://github.com/dotnet/runtime/issues/25485#issuecomment-467261945


注意事项:

  • 带有正文的HTTP GET请求是一种有些不寻常的结构,属于HTTP规范的灰色地带。结果是许多旧软件要么根本无法处理这样的请求,要么会明确拒绝它,因为它们认为它是格式错误的。您需要非常确定您试图发送此类请求的端点是否支持它,否则最好情况下您将收到一个HTTP错误代码;最坏的情况是正文将被默默丢弃。这可能导致一些令人头疼的调试!
  • 缓存代理服务器,特别是旧版本的,可能只基于URL缓存GET请求,因为它们不希望存在正文。这可能会导致最近的请求永久缓存(这将破坏您的软件),或者唯一缓存的请求是最近发出的请求(这将防止缓存按预期工作)。同样,这可能非常痛苦。

1
这会引发 ProtocolViolationException 异常。 - Alisson Reinaldo Silva
1
@Alisson 你在使用 .NET Core 吗? - Ian Kemp
嗨,伊恩!不,我正在使用.NET 4.7,这只适用于.NET Core吗?谢谢! - Alisson Reinaldo Silva
4
在 .Net 4.7.2 中,我使用 ContentType.Json 时一直出现错误,所以我不得不使用 "application/json" - computercarguy

36

我无法使用.NET Core,也不想安装 System.Net.Http.WinHttpHandler,因为它有很多依赖项。我通过使用反射解决了这个问题,欺骗 WebRequest 发送正文内容是合法的 GET 请求(这是根据最新的 RFC)。我所做的是将 HTTP 动词“GET”的 ContentBodyNotAllowed 设置为 false。

var request = WebRequest.Create(requestUri);

request.ContentType = "application/json";
request.Method = "GET";

var type = request.GetType();
var currentMethod = type.GetProperty("CurrentMethod", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(request);

var methodType = currentMethod.GetType();
methodType.GetField("ContentBodyNotAllowed", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(currentMethod, false);

using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
    streamWriter.Write("<Json string here>");
}

var response = (HttpWebResponse)request.GetResponse();

请注意,属性 ContentBodyNotAllowed 属于静态字段,因此当其值更改时,其影响将在整个程序中保持不变。对于我的目的来说,这不是问题。


2
救命稻草!非常感谢这个解决方案。完美地解决了问题!! - codemonkeytony
1
一个巧妙的解决方法!只需注意,由于这使用了反射,不能保证它将继续工作(例如,如果Microsoft重命名ContentBodyNotAllowed)。 - Ian Kemp
你有没有可能模拟它,以便在单元测试中覆盖它? - justinb
1
@justinb 我在单元测试中没有使用过它。 - alfoks

2
我使用 WebRequest。 该值存储在变量 json 中。
            var request = WebRequest.Create(requestUri);

            System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
            
            request.ContentType = "application/json";
            request.Method = "GET";
            request.Headers.Add("Authorization", "Bearer " + Token);

            var type = request.GetType();
            var currentMethod = type.GetProperty("CurrentMethod", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(request);

            var methodType = currentMethod.GetType();
            methodType.GetField("ContentBodyNotAllowed", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(currentMethod, false);

            using (var streamWriter = new StreamWriter(request.GetRequestStream()))
            {
                streamWriter.Write(JsonConvert.SerializeObject(Entity));
            }

            var response = request.GetResponse();
            var responseStream = response.GetResponseStream();
            if (responseStream != null)
            {
                var myStreamReader = new StreamReader(responseStream, Encoding.Default);
                var resultEntity= myStreamReader.ReadToEnd();
                myStreamReader.ReadToEnd());
            }
            responseStream.Close();
            response.Close();

1

花了很多时间后,我无法使HttpClientWebRequest在我的.Net Core项目中工作,明显地,服务器没有获取到我的数据,并返回一个错误,说我的请求中缺少某些特定的数据。最终,只有RestSharp在我的情况下起作用(以下代码中的变量myData是一个Dictionary<string,string>实例):

var client = new RestClient("some url");
var request = new RestRequest(Method.GET);
foreach (var d in myData)
    request.AddParameter(d.Key, d.Value);
var result = (await client.ExecuteAsync(request)).Content;

-2

类似于上面的答案,但代码更简洁

var request = new HttpRequestMessage
{
    Method = HttpMethod.Get,
    RequestUri = targetUri,
    Content = new StringContent(payload.Payload),
};
var response = await client.SendAsync(request).ConfigureAwait(false);
var responseInfo = await response.Content.ReadAsStringAsync();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接