2

I am probably missing something really simple but I just cannot find it.

An API I am working with demands the inclusion of a Content-Type header even with GETs.

I am struggling to add the header to my request.

Here is the code that builds the httpclient:

public static async Task<HttpClient> Authenticate(string baseUrl, string clientId, string clientSecret)
    {
        HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");

        string toBeHashed = $"client_id={clientId}&client_secret={clientSecret}";
        string hashed = sha256_hash(toBeHashed);

        AuthRequest request = new AuthRequest
        {
            client_id = clientId,
            hash = hashed
        };

        StringContentWithoutCharset body = new StringContentWithoutCharset(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");

        HttpResponseMessage response = await httpClient.PostAsync($"{baseUrl}authenticate", body);

        AuthResponse result = await JsonSerializer.DeserializeAsync<AuthResponse>(await response.Content.ReadAsStreamAsync());

        httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.data.token);
        

        return httpClient;
    }

And here is 2 different attempts at making a GET call.

When I look in Fiddler the Content-Type is missing from the headers:

public class Functions
{
    private HttpClient _httpClient;
    private readonly string _baseUrl;

    public Functions(string baseUrl, string clientId, string clientSecret)
    {
        _httpClient = Helpers.Authenticate(baseUrl, clientId, clientSecret).Result;
        _baseUrl = baseUrl;
    }

    public async Task ProductList()
    {
        // this doesn't work
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"{_baseUrl}products");
        request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
        HttpResponseMessage response1 = await _httpClient.SendAsync(request);
        ProductList result1 = await JsonSerializer.DeserializeAsync<ProductList>(await response1.Content.ReadAsStreamAsync());

        // nor does this
        _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
        HttpResponseMessage response = await _httpClient.GetAsync($"{_baseUrl}products");
        ProductList result2 = await JsonSerializer.DeserializeAsync<ProductList>(await response.Content.ReadAsStreamAsync());
    }
}

Any help would be greatly appreciated.

Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89
  • Maybe the header already has `Content-Type` entry. Also, you can check the return of `TryAddWithoutValidation` – vernou Sep 29 '20 at 12:40
  • @Vernou the result of TryAddWithoutValidation is false and there is no Content-Type Entry in Fiddler... although it was when i first called for the API token. Could that affect it? – Trevor Daniel Sep 29 '20 at 12:42
  • Content-Type is to specify the content's type of the request, but GET request don't have a content. I miss something in your question? – vernou Sep 29 '20 at 12:46
  • I completely agree! But, this API responds with a 400 Bad Request when it's not present even on a GET. When i add the content-type manually in Fiddler i get a result for the products. Their API shouldn't demand this content-type on a GET but it does :( – Trevor Daniel Sep 29 '20 at 12:52
  • See following : https://stackoverflow.com/questions/10679214/how-do-you-set-the-content-type-header-for-an-httpclient-request – jdweng Sep 29 '20 at 12:56
  • 1
    Hacky, but works: just add this line: `request.Content = new StringContent("", Encoding.UTF8, "application/json");` This should add the header. ~~Alternatively, if you don't want to specify the encoding, then add an empty `StringContent`, then `request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/json");`~~ (edit: the last one doesn't work properly) – Riwen Sep 29 '20 at 13:21
  • @Riwen that worked!! I love you! – Trevor Daniel Sep 29 '20 at 13:24
  • 1
    Haha, you're welcome man. – Riwen Sep 29 '20 at 13:25

2 Answers2

3

Reposting my comment as an answer, so that it's helpful for everyone facing the same.

You can't use Content-Type as request header as godot pointed out, because it's not really that, as far as .NET is concerned. However, it is also a "content header" (which is a request header, technically speaking). Content headers are perfectly valid for POST and PUT requests. Therefore, as a hacky solution (GET requests are not supposed to have content), you can add this line:

request.Content = new StringContent("", Encoding.UTF8, "application/json");

This adds an empty StringRequest, and as the third argument, you can actually specify the Content-Type.

Riwen
  • 4,734
  • 2
  • 19
  • 31
1

You are using response header as a request header.

TryWithoutValidation method returns false in your example because you are using response header as a request header. If you try to use Add method instead of TryWithoutValidation you see actual reason of error.

request.Headers.Add("content-type", "application/json");

and you receive:

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'

So use Accept header instead of Content-type

request.Headers.TryAddWithoutValidation("accept", "application/json");
godot
  • 3,422
  • 6
  • 25
  • 42
  • My request already contains "Accept: application/json". As explained in my question the API I am working with is incorrectly demanding that the "Content-Type" header is present even for GETs – Trevor Daniel Sep 29 '20 at 13:07
  • so you can't have "Content-Type" header in your request, because it is a response header – godot Sep 29 '20 at 13:09
  • 1
    @godot yes, your explanation is correct, but OPs API is not. Thats why OP asked how to add a header to a GET request. – nilsK Sep 29 '20 at 14:49