【问题标题】:How to use HttpClient to send content in body of GET request?如何使用 HttpClient 在 GET 请求正文中发送内容?
【发布时间】:2017-04-15 01:07:25
【问题描述】:

目前要向 API 接口发送参数化的 GET 请求,我正在编写以下代码:

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

但我发现 URL 长度有 2,083 个字符的限制。

为避免这种情况,我想在 GET 请求的内容正文中以 json 格式发送参数。

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

有没有办法为不在 URL 中的 GET 请求发送参数以避免 URL 长度限制?

【问题讨论】:

  • 看看这个:stackoverflow.com/questions/978061/http-get-with-request-body 所以基本上,即使理论上你可以做到这一点,但你真的不应该这样做。
  • 您不能使用 GET 请求发送正文,不能使用 HttpClient 或 WebClient 或其他任何东西。但即使您设法在低级别执行此操作,服务器也不会解析正文,因为它会将其视为 GET 请求。
  • 您不能这样做的原因是,根据定义,GET 旨在检索资源。不太可能有人需要那么多字符来检索资源。您更有可能打算提交数据而不是重新获取数据,这是 POST 的设计目的。 w3.org/Protocols/rfc2616/rfc2616-sec9.html
  • 可能是 GET,例如 getbyWhere? id=&id=.... 但是它达到了 4K 限制,使它成为一个帖子也不好。
  • @SelmanGenç 错了,看我的回答。

标签: c# get request dotnet-httpclient


【解决方案1】:

请阅读此答案末尾的注意事项,了解为什么通常不建议使用带有正文的 HTTP GET 请求。


  • 如果您使用的是 .NET Core,标准的 HttpClient 可以开箱即用地执行此操作。例如,发送带有 JSON 正文的 GET 请求:

      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 不支持这种开箱即用(如果您尝试上述代码,您将收到ProtocolViolationException)。值得庆幸的是,Microsoft 提供了 System.Net.Http.WinHttpHandler 包,确实支持该功能 - 只需在构建 HttpClient 实例时安装并使用它而不是默认的 HttpClientHandler:

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

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


注意事项:

  • 带有正文的 HTTP GET 是一种非常规的结构,属于 HTTP 规范的灰色区域 - 最终结果是许多旧软件要么根本无法处理此类请求,要么会明确拒绝它,因为它们相信它是畸形的。您需要非常确保您尝试发送此类请求的端点确实支持它,或者您最多会得到一个 HTTP 错误代码;在最坏的情况下,尸体会被默默地丢弃。这可能会导致一些令人头疼的调试!
  • 缓存代理服务器,尤其是较旧的代理服务器,可能仅基于 URL 缓存 GET 请求,因为它们不希望出现正文。这可能导致最近的请求被永久缓存(这将破坏您的软件),或者唯一缓存的请求是最近发出的请求(这将阻止缓存按预期工作)。同样,要弄清楚这一点可能会非常痛苦。

【讨论】:

  • 这会引发 ProtocolViolationException。
  • @Alisson 你在使用 .NET Core 吗?
  • 嗨伊恩!不,我使用的是 .NET 4.7,这是否仅适用于 .NET Core?谢谢!
  • 在 .Net 4.7.2 中,ContentType.Json 不断出现错误,因此我不得不使用 "application/json"。
【解决方案2】:

我不能使用 .NET 核心,也不想安装有大量依赖项的 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 属于静态字段,因此当其值更改时,它对程序的其余部分仍然有效。这对我来说不是问题。

【讨论】:

  • 救命!非常感谢这个解决方案。完美!
  • 一个很好的解决方法!请注意,由于这使用反射,因此不能保证它会继续工作(例如,如果 Microsoft 重命名 ContentBodyNotAllowed)。
  • 你有没有机会模拟这个,以便在单元测试中覆盖它?
  • @justinb 我没有在单元测试中使用过它
【解决方案3】:

花了很多时间后,我无法让 HttpClient 和 WebRequest 在我的 .Net Core 项目中工作,显然服务器没有获取我的数据,它返回一个错误,说某些特定数据不可用在我的要求中。最后只有RestSharp 在我的情况下工作(变量myData 是以下代码中的Dictionary&lt;string,string&gt; 实例):

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;

【讨论】:

    【解决方案4】:

    我使用 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();
    

    【讨论】:

      【解决方案5】:

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

      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();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-06
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        相关资源
        最近更新 更多