32

Currently, I am working on API wrapper. If I send a bad Consumer Key, the server will return Status as 403 Forbidden in the header. It will also pass custom headers. How do I actually retrieve these custom headers?

This is the response receive from the server.

Cache-Control: private
Date: Wed,  01 May 2013 14:36:17 GMT
P3P: policyref="/w3c/p3p.xml",  CP="ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE"
Server: Apache/2.2.23 (Amazon)
Status: 403 Forbidden
X-Error: Invalid consumer key.
X-Error-Code: 152
X-Powered-By: PHP/5.3.20
Connection: keep-alive

I need to retrieve the X-Error and X-Error-Code. Currently, I am using HttpClient class to process the request. If I watch the headers respond under Quick Watch in VS Studio 2012, I could find it like this

((System.Net.Http.Headers.HttpHeaders)(response.Headers)).headerStore["X-Error-Code"].ParsedValue

Is there any other way to do this?

Edit: headerStore is not accessible thru code as this is private field. I only get access to it through the Quick Watch window.

This is my snippet for the request:

var response = await _httpClient.PostAsync("/v3/oauth/request", content);
Shulhi Sapli
  • 1,566
  • 3
  • 16
  • 27

5 Answers5

45

Well, HttpResponseMessage.Headers returns an HttpResponseHeaders reference, so you should be able to use GetValues()

string error = response.Headers.GetValues("X-Error").FirstOrDefault();
string errorCode = response.Headers.GetValues("X-Error-Code").FirstOrDefault();
Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • Nope, I can't do that as I would receive this error `Error 3Cannot apply indexing with [] to an expression of type System.Net.Http.Headers.HttpResponseHeaders'`. I'll update my code above for clarity. – Shulhi Sapli May 01 '13 at 15:17
  • @ShulhiSapli: Ah - hadn't spotted that it was `HttpClient`. Will edit. – Jon Skeet May 01 '13 at 15:21
  • If you requires more detail, you can take a look here, https://github.com/shulhi/PocketNet/blob/dev/PocketNet/PocketNet/Authenticator/PocketOauth.cs – Shulhi Sapli May 01 '13 at 15:30
30

Since the title of the question is "retrieve all headers", I wanted to add an answer in regards to that.

The HttpResponseMessage returned by HttpClient methods has two header properties:

  • HttpResponseMessage.Headers is an HttpResponseHeaders with generic response headers
  • HttpResponseMessage.Content.Headers is an HttpContentHeaders with content-specific headers like Content-Type

Both objects implement IEnumerable<KeyValuePair<string, IEnumerable<string>>, so you can easily combine all the headers with something like this:

var responseMessage = await httpClient.GetAsync(url);
var headers = responseMessage.Headers.Concat(responseMessage.Content.Headers);

// headers has type IEnumerable<KeyValuePair<String,IEnumerable<String>>>

The reason it's an-enumerable-set-of-names-with-multiple-values is because some HTTP headers (like Set-Cookie) can be repeated in a response (even though the majority of other headers can only appear once - but software should gracefully handle an RFC-violating webserver returning invalid headers).

Generating a string of all headers:

We can generate a flat string of headers using a single Linq expression:

  • Use Concat to combine both HttpResponseMessage.Headers and HttpResponseMessage.Content.Headers.
    • Don't use Union because that won't preserve all headers.
    • (As a personal style preference, when I'm concatenating two IEnumerable<T> objects together, I start off with Enumerable.Empty<T>() for visually symmetrical results - not for performance or any other reason).
  • Use .SelectMany on each Headers collection to flatten each collection before concatenating their flat results.
  • Use Aggregate with a StringBuilder to efficiently generate a string representation.

Like so:

    HttpResponseMessage resp = await httpClient.GetAsync( url );

    String allHeaders = Enumerable
        .Empty<(String name, String value)>()
        // Add the main Response headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
         // Concat with the content-specific headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Content.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
        // Render to a string:
        .Aggregate(
            seed: new StringBuilder(),
            func: ( sb, pair ) => sb.Append( pair.name ).Append( ": " ).Append( pair.value ).AppendLine(),
            resultSelector: sb => sb.ToString()
        );

Loading all headers into a NameValueCollection:

Another alternative is to use the classic NameValueCollection class from .NET Framework 1.1, which supports keys with multiple values (indeed, it's used in Classic ASP.NET WebForms for this purpose):

Like so:

    HttpResponseMessage resp = await httpClient.GetAsync( url );

    NameValueCollection allHeaders = Enumerable
        .Empty<(String name, String value)>()
        // Add the main Response headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
         // Concat with the content-specific headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Content.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
        .Aggregate(
            seed: new NameValueCollection(),
            func: ( nvc, pair ) => { nvc.Add( pair.name, pair.value ); return nvc; },
            resultSelector: nvc => nvc
        );
Dai
  • 110,988
  • 21
  • 188
  • 277
Nick
  • 3,062
  • 2
  • 21
  • 17
  • This creates a very complicated type for the headers variable. How do get the data out to a string or a simple collection? – Jamie Marshall May 11 '17 at 20:18
  • @JamieMarshall Since it puts everything into an IEnumerable of key/value pairs you should be able to use a foreach loop to go through the combined list. The key of each pair is the header name, and the value of each pair is the list of header values. – Ben Sutton Aug 16 '17 at 18:55
  • It's worth noting that for the `Concat` to work you need to make sure you include `using System.Linq;` – Ben Sutton Aug 16 '17 at 18:56
13

Just a gotcha that I found when attempting to find a header that didn't exist. You should use TryGetValues instead of GetValues because at runtime it will throw an exception if the header is not found. You would use something like this code:

IEnumerable<string> cookieHeader; 
response.Headers.TryGetValues("Set-Cookie", out cookieHeader);
Cameron Tinker
  • 9,119
  • 10
  • 42
  • 80
3

A bit bulky, but simple to understand..

            System.Diagnostics.Debug.Write("----- CLIENT HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpClient.DefaultRequestHeaders)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach(string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }

            System.Diagnostics.Debug.Write("----- MESSAGE HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpRequestMessage.Headers)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach (string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }

            System.Diagnostics.Debug.Write("----- CONTENT HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpRequestMessage.Content.Headers)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach (string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }
da_jokker
  • 794
  • 7
  • 14
0

This works for me:

(String[])response.Headers.GetValues("X-Error"))[0]
Nisse Engström
  • 4,555
  • 22
  • 24
  • 38
nmishr
  • 215
  • 1
  • 6
  • I'm curious for the reason on the down vote on this Nisse. I included it here as its from a console app I'm using. – nmishr Sep 26 '17 at 17:04
  • I downvoted this too because it's unsafe: The specification for `HttpHeaders` does not guarantee that `.GetValues(String)` will return a `String[]` (it only says it returns an `IEnumerable`), and if there are no values for the specified name then you'll get an `InvalidOperationException` - and if the same header is repeated then this will only return the first value for the specified header name. – Dai Apr 22 '20 at 18:23
  • Another reason to downvote this answer is because the OP asked how to get "all headers" but this answer only returns the value of a single named header - so this doesn't answer the original question at all. – Dai Apr 22 '20 at 18:24