21

I have a static httpclient shared across requests and I want to add one custom header to it.

httpClient.DefaultRequestHeaders.Add("customHeader", somevalue.ToString());

But I noticed that on every request the value is getting added to that header which I intend to replace on each request. I try to remove the header if it is already exist and add again but it gives me an errors on load test.

if (httpClient.DefaultRequestHeaders.Contains("customHeader"))
        {
            httpClient.DefaultRequestHeaders.Remove("customHeader");
        }
httpClient.DefaultRequestHeaders.Add("customHeader",somevalue.ToString());

Errors -

System.ArgumentException: An item with the same key has already been added.
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
System.ArgumentNullException: Value cannot be null.

How can I update the custom header value on each request?

mahesh_ing
  • 419
  • 1
  • 3
  • 10
  • 1
    don't add it to the `DefaultRequestHeaders`, but to the actual Request itself? – Icepickle Feb 10 '17 at 10:34
  • 3
    Using the HttpClient.DefaultRequestHeaders object at the same time (concurrent) will cause problems. you should Manage headers via HttpRequestMessage. – levent Feb 10 '17 at 12:40
  • I suggest you should not use shared static variable. static variable shared thread which causes concurrent problem. I don't use static httpclient. It resolved already. – funbrain9 Jun 11 '20 at 03:58
  • @funbrain9 what is your point on that? Instead, reusing sockets is a good approach https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ – anatol Oct 11 '21 at 08:42

4 Answers4

7

The error I was getting: An item with the same key has already been added. Key: x

Example code for mahesh_ing answer:

var request = new HttpRequestMessage
{
    Method = this.method,
    RequestUri = new Uri(this.requestUri),
};

request.Headers.Add("Key", "Value");

var client = new System.Net.Http.HttpClient
{
    Timeout = this.timeout
};

return await client.SendAsync(request);
Gerald Hughes
  • 5,771
  • 20
  • 73
  • 131
5

I added header to actual (current) request using HttpRequestMessage and replaced a call with SendAsync instead of GetAsync and it resolved my issue. Thanks @levent.

mahesh_ing
  • 419
  • 1
  • 3
  • 10
5

I had the same issue with the httpClient Default request headers. See below for example using HttpRequestMessage.headers instead.

 _httpClient.DefaultRequestHeaders.Clear();
 _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));

        XDocument requestXml = JsonConvert.DeserializeXNode(message.ToString());

        HttpRequestMessage webRequest = new HttpRequestMessage()
        {
            Content = new StringContent(requestXml.Document.ToString().Replace("\r\n", string.Empty), Encoding.UTF8, "text/xml"),
            Method = HttpMethod.Post,
            RequestUri = new Uri(uri),
        };
        webRequest.Headers.Add("CorrelationId", correlationId);
        webRequest.Headers.Add("SOAPAction", endpointSOAPAction);

I was previously using the Default Request Headers for my correlationid and soap action.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
user2350749
  • 51
  • 1
  • 2
1

When deciding how to assign Headers to the HttpClient consider if the Headers will be the same for every request especially if the HttpClient instance is shared across all threads.

If the Headers will always be the same the DefaultRequestHeaders can be used and only needs to be assigned once.

Any changes made to DefaultRequestHeaders will affect all threads accessing the instance.

The example below is for .NET Core and .NET 7

// include dependencies
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

// check header has not already been set
if (!_httpClient.DefaultRequestHeaders.Contains("key"))
{
    _httpClient.DefaultRequestHeaders.Add("key", "value");
}

// test value exists
var headerValue = string.Empty;
    
if (_httpClient.DefaultRequestHeaders.Contains("key"))
{
    headerValue = _httpClient.DefaultRequestHeaders.GetValues("key").First();
}

// any other configuration...

// invoke the request using GetAsync
var response = await _httpClient.GetAsync("path");

var returnValue = string.Empty;

if (response.IsSuccessStatusCode)
{
    returnValue = await response.Content.ReadAsStringAsync();
}

However if the Headers are expected to change for each message, then instead setup a HttpRequestMessage instance while initializing each new request with the required values.

// include dependencies
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

// use shared single instance to avoid port exhaustion in constructor etc.
_httpClient = new HttpClient();

// media-type accept header only needs to be assigned once
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

// initialize request
_httpClient.BaseAddress = new Uri("https://example.com/");

var headersDictionary = new Dictionary<string, string>()
                        {
                            {"key1", "value1"},
                            {"key2", "value2"}
                        };

// create instance of HttpRequestMessage
var httpRequestMessageObject = new HttpRequestMessage(HttpMethod.Get, "relativePath");

var body = "content";

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

// check keys are not duplicated
foreach (var header in headersDictionary)
{
    if (!httpRequestMessageObject.Headers.Contains(header.Key))
    {
        httpRequestMessageObject.Headers.Add(header.Key, header.Value);
    }
}

// test value exists
var headerValue = string.Empty;

if (httpRequestMessageObject.Headers.Contains("key"))
{
    headerValue = httpRequestMessageObject.Headers.GetValues("key").First();
}

// invoke the request using SendAsync
var response = await _httpClient.SendAsync(httpRequestMessageObject);

var returnValue = string.Empty;

if (response.IsSuccessStatusCode)
{
    returnValue = await response.Content.ReadAsStringAsync();
}

Reference

This is demonstated further in the following Stack Overflow answers:

  1. https://stackoverflow.com/a/37929393/1165173
  2. https://stackoverflow.com/a/10679340/1165173
nimblebit
  • 473
  • 3
  • 11
  • 22