1

I need to use 3rd party web api.
On this specific endpoint I need to make GET request with content body (json).

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

I targeted that code to netstandard2.0 and it worked.
However I now need to use in project where I target net461 and it throws exception saying "Cannot send a content-body with this verb-type"

I understand that it is not usual to set content to GET request, but that api is out of my reach.

  1. How come HttpClient.Send(request) failed when I target net461, but works well when I target netstandard2.0?
  2. What options do I have when I target net461?

Update

A way to reproduce.

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.stackoverflow.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 shows 2.1.104

Community
  • 1
  • 1
Siim Haas
  • 485
  • 2
  • 6
  • 15
  • 1
    "it is not usual to set content to GET request"... If you're asking how to reproduce buggy behaviour on a platform that performs correctly, I think you'll be out of luck. Sending a request body with a GET may not break some implementations, but pass that request through some intermediate hardware, and there's a good chance that the request will blow up. I'd ditch this as a strategy and stick to standardised behaviour rather than RFC/standards-breaking behaviour. Effectively, you're saying "I like this bug... more bugs please". – spender Apr 30 '18 at 13:30
  • Can you please run both the commands again and capture the network trace in any tool (like fiddler) and see the difference between two request payload. – user1672994 Apr 30 '18 at 13:33
  • @xxbbcc there are very popular products which designed their api using GET requests with body. One example is Elastic search. Here is an example of such api: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html – Evk Apr 30 '18 at 13:40
  • @Evk Ok - I call that unexpected (even if it's allowed by the RFC). – xxbbcc Apr 30 '18 at 13:43
  • 1
    @Evk - but at least that API acknowledges that problems exist with GET+body and allow POST as an alternative. Anybody offering GET+body only is in a murky world of possible interop issues. – Damien_The_Unbeliever Apr 30 '18 at 13:44
  • Hmm. My comment above seems a little stale now. I'm still not convinced that there's ever a good use for GET bodies, and would grumble mightly if ever confronted with the necessity to use one. It definitely contradicts the rules of least-astonishment. – spender Apr 30 '18 at 13:45
  • @Damien_The_Unbeliever I don't think they acknowledge that. They propose GET with body as a main method, and mention POST as alternative because "not all clients support GET with body", not because it's inherently wrong or might lead to issues you describe. They don't offer that because of backwards compatibility or something, they think it's perfectly fine. – Evk Apr 30 '18 at 13:46
  • @spender - it's why people keep looking to introduce a new verb (`SEARCH` is an often mooted example) which is GET-like semantics with a body and better definitions for cachability. – Damien_The_Unbeliever Apr 30 '18 at 13:46
  • I tried POST, but that failed. I can contact the that 3rd party and hope that they can allow POST requests as alternative, but currently I'd like to know if I can manage it on my side. – Siim Haas Apr 30 '18 at 13:59

1 Answers1

3

You didn't mention on which .net version you run it when it works (because you can target net standard but you cannot run on net standard). So I assume that when it works you run it on .NET Core, and when it does not you run on full .NET 4.6.1 as you said.

Then it's just because it was implemented differently in .NET Core and full .NET Framework, as described in this issue. There is nothing wrong with either implementation, because RFC says:

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.

So, having body in GET (while almost never a good practice) does not by itself contradict that RFC. One implementation (older) was designed to reject it, but another (newer) was designed to allow it, there is nothing more to that.

As for your second question "What options do I have when I target net461". I don't think there are easy workarounds, because that's the behavior of HttpWebRequest, not specifically of HttpClient. And almost anything that makes web requests uses HttpWebRequest internally.

There are ugly hacks using reflections though. Your example can be modified like this (works only when running on full .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.stackoverflow.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);            
}

Basically we use reflection to modify internal field named ContentBodyNotAllowed of KnownHttpVerb.Get to false. Use at your own risk.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • The issue with this is the next line from the RFC, not quoted above "The response to a GET request is cacheable". Together with "payload ... has no defined semantics" means that caches are allowed, by most readings of the RFC, to cache based *only* on the request headers and should *ignore* the body in making caching decisions. – Damien_The_Unbeliever Apr 30 '18 at 13:56
  • @Damien_The_Unbeliever point is that it's not explicitly forbidden by RFC, not that it's a good idea or has no problems. And for that reason, one I suppose cannot consider this a bug. – Evk Apr 30 '18 at 13:57
  • That github issue is interesting and answers question no 1. In that issue "karelz" said "there is easy workaround and the scenario is very corner-case". Do you know what is the work around? – Siim Haas Apr 30 '18 at 14:05
  • @SiimHaas I think he just means not use GET requests with body, and use POST instead. I don't think he means any specific workaround to allow GET with body. However, I expanded answer a bit to show ugly hack with reflection to do that. – Evk Apr 30 '18 at 14:11
  • @Evk I though there was an easy work around. This is indeed ugly. Thank you for the answer. – Siim Haas May 01 '18 at 18:38
  • @SiimHaas yes ugly, but works (and I don't think there are any side effects). So if you really need to - why not. – Evk May 01 '18 at 19:10