178

I currently have a message handler in my Web API service that overrides 'SendAsync' as follows:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
  //implementation
}

Within this code I need to inspect a custom added request header value named MyCustomID. The problem is when I do the following:

if (request.Headers.Contains("MyCustomID"))  //OK
    var id = request.Headers["MyCustomID"];  //build error - not OK

...I get the following error message:

Cannot apply indexing with [] to an expression of type 'System.Net.Http.Headers.HttpRequestHeaders'

How can I access a single custom request header via the HttpRequestMessage (MSDN Documentation) instance passed into this overridden method?

atconway
  • 20,624
  • 30
  • 159
  • 229

13 Answers13

294

Try something like this:

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");
var id = headerValues.FirstOrDefault();

There's also a TryGetValues method on Headers you can use if you're not always guaranteed to have access to the header.

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Youssef Moussaoui
  • 12,187
  • 2
  • 41
  • 37
  • 28
    The null check for GetValues doesn't serve any value as it will never return null. If the header doesn't exist you will get an InvalidOperationException. You need to use TryGetHeaders if it's possible the header might not exist in the request and check for a false response OR try/catch around the GetValues call (not recommended). – Drew Marsh Feb 19 '13 at 22:58
  • 5
    @Drew request.Headers.Single(h => h.Key == "Authorization"); Much less code doing the same! – Elisabeth Jan 30 '14 at 19:40
  • 21
    Why not just `var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();` – Gaui Oct 14 '14 at 23:08
  • 4
    @SaeedNeamati because header values are not one-to-one. You can have `Some-Header: one` and then `Some-Header: two` in the same request. Some languages silently discard "one" but that's incorrect. It's in the RFC but I'm too lazy to find it now. – Cory Mawhorter Jun 29 '16 at 00:45
  • 1
    Saeed's point is valid, usability is important and the most common use case here is to retrieve one value for a request header. You can still have a GetValues operation for retrieving multiple values for a request header (which people will arely use), but 99% of the time they'll want to just retrieve one value for a specific request header, and that should be a one liner. – Justin Jul 18 '16 at 14:14
  • I got it using Request class instead of request, the rest should be the same. – CoderRoller Mar 14 '17 at 14:55
  • how do you get the request variable with the current request instance? I need the instance to be available in the controller method. Also what is the type of the request variable and what namespaces do I need to include in the using? – lohiarahul Jul 27 '17 at 06:25
  • `TryGetValues` doesn't work with custom headers? – Toolkit May 23 '22 at 13:15
48

The line below throws exception if the key does not exists.

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");

Safe solution using TryGetValues:

Include System.Linq;

IEnumerable<string> headerValues;
var userId = string.Empty;

     if (request.Headers.TryGetValues("MyCustomID", out headerValues))
     {
         userId = headerValues.FirstOrDefault();
     }           
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
SharpCoder
  • 18,279
  • 43
  • 153
  • 249
17

To expand on Youssef's answer, I wrote this method based Drew's concerns about the header not existing, because I ran into this situation during unit testing.

private T GetFirstHeaderValueOrDefault<T>(string headerKey, 
   Func<HttpRequestMessage, string> defaultValue, 
   Func<string,T> valueTransform)
    {
        IEnumerable<string> headerValues;
        HttpRequestMessage message = Request ?? new HttpRequestMessage();
        if (!message.Headers.TryGetValues(headerKey, out headerValues))
            return valueTransform(defaultValue(message));
        string firstHeaderValue = headerValues.FirstOrDefault() ?? defaultValue(message);
        return valueTransform(firstHeaderValue);
    }

Here's an example usage:

GetFirstHeaderValueOrDefault("X-MyGuid", h => Guid.NewGuid().ToString(), Guid.Parse);

Also have a look at @doguhan-uluca 's answer for a more general solution.

neontapir
  • 4,698
  • 3
  • 37
  • 52
  • 1
    `Func` and `Action` are generic delegate signature constructs built into .NET 3.5 and above. I'd be happy to discuss specific questions about the method, but I'd recommend learning about those first. – neontapir Dec 02 '13 at 18:52
  • 1
    @neontapir (and others) the second parameter is used to provide a default value if the key is not found. The third parameter is used to 'transform' the return value to be of the desired type which also specifies the type to be returned. Per the example, if 'X-MyGuid' is not found, parameter 2 lambda basically supplies a default guid as a string (as it would have been retrieved from Header) and the Guid.Parse third parameter will translate the found or default string value into a GUID. – Mikee Feb 06 '15 at 14:23
  • @neontapir where is Request coming from in this function? (and if it's null how will a new HttpRequestMessage() have any headers? doesn't it make sense to just return the default value if Request is null? – mendel Apr 30 '15 at 16:58
  • It's been two years, but if I recall, a new `HttpRequestMessage` is initialized with an empty Headers collection, which isn't null. This function does end up returning the default value if request is null. – neontapir Apr 30 '15 at 17:10
  • @mendel, neontapir I've tried using the above snippet and I believe the "Request" on line 2 of the method body should either be a private field in the class containing the method or be passed as a parameter (of type HttpRequestMessage) to the method – Sudhanshu Mishra Jul 06 '15 at 01:35
  • @dotnetguy, you're right, it was, along the lines of `Request = new HttpRequestWrapper(HttpContext.Current.Request);`. See http://stackoverflow.com/questions/3260934/mocking-httprequest-in-asp-net-4-0 for more. – neontapir Jul 06 '15 at 05:59
13

Create a new method - 'Returns an individual HTTP Header value' and call this method with key value everytime when you need to access multiple key Values from HttpRequestMessage.

public static string GetHeader(this HttpRequestMessage request, string key)
        {
            IEnumerable<string> keys = null;
            if (!request.Headers.TryGetValues(key, out keys))
                return null;

            return keys.First();
        }
SRI
  • 190
  • 2
  • 5
10

To further expand on @neontapir's solution, here's a more generic solution that can apply to HttpRequestMessage or HttpResponseMessage equally and doesn't require hand coded expressions or functions.

using System.Net.Http;
using System.Collections.Generic;
using System.Linq;

public static class HttpResponseMessageExtensions
{
    public static T GetFirstHeaderValueOrDefault<T>(
        this HttpResponseMessage response,
        string headerKey)
    {
        var toReturn = default(T);

        IEnumerable<string> headerValues;

        if (response.Content.Headers.TryGetValues(headerKey, out headerValues))
        {
            var valueString = headerValues.FirstOrDefault();
            if (valueString != null)
            {
                return (T)Convert.ChangeType(valueString, typeof(T));
            }
        }

        return toReturn;
    }
}

Sample usage:

var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");
Doguhan Uluca
  • 6,933
  • 4
  • 38
  • 51
  • Looks great, but `GetFirstHeaderValueOrDefault` has two parameters, so it complains about missing param when calling as the sample usage `var myValue = response.GetFirstHeaderValueOrDefault("MyValue");` Am I missing something? – Jeb50 Apr 15 '17 at 05:20
  • 1
    Added the new static class, replaced Response for Request. Called from API controller, as `var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault("productID");` got **There is no argument given that corresponds to the required formal parameter 'headerKey' of 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault(HttpRequestMessage, string)'** – Jeb50 Apr 15 '17 at 05:42
  • @Jeb50 are you declaring `using HttpResponseMessageExtensions` on the file you're attempting to use this extension? – Doguhan Uluca Apr 26 '17 at 15:11
9

One line solution (assuming that the header exists)

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
  • What if MyCustomID is not part of request.. it returns null exception. – Prasad Kanaparthi Apr 03 '20 at 16:02
  • 2
    @PrasadKanaparthi then you should use another answer like https://stackoverflow.com/a/25640256/4275342. You see that there is no any null check, so, what is `request` is `null`? It is also possible. Or what if `MyCustomID` is an empty string or not equals to `foo`? It depends on context, so this answer just describes the way, and all validation and business logic you need to add by your own – Roman Marusyk Apr 03 '20 at 16:09
  • Don't you agree that the code is working and can return header value? – Roman Marusyk Apr 03 '20 at 16:15
  • 1
    It works fine.. if "MyCustomID" is part of request of request. Yes, all validation need to taken care – Prasad Kanaparthi Apr 04 '20 at 14:24
  • 2
    @PrasadKanaparthi, if the header doesn't exist you will get an InvalidOperationException, not null – Michael Freidgeim Jun 10 '21 at 22:20
6

For ASP.NET you can get the header directly from parameter in controller method using this simple library/package. It provides a [FromHeader] attribute just like you have in ASP.NET Core :). For example:

    ...
    using RazHeaderAttribute.Attributes;

    [Route("api/{controller}")]
    public class RandomController : ApiController 
    {
        ...
        // GET api/random
        [HttpGet]
        public IEnumerable<string> Get([FromHeader("pages")] int page, [FromHeader] string rows)
        {
            // Print in the debug window to be sure our bound stuff are passed :)
            Debug.WriteLine($"Rows {rows}, Page {page}");
            ...
        }
    }
lawrenceagbani
  • 147
  • 1
  • 11
4

For ASP.Net Core there is an easy solution if want to use the param directly in the controller method: Use the [FromHeader] annotation.

        public JsonResult SendAsync([FromHeader] string myParam)
        {
        if(myParam == null)  //Param not set in request header
        {
           return null;
        }
        return doSomething();
    }   

Additional Info: In my case the "myParam" had to be a string, int was always 0.

Reiner
  • 1,621
  • 1
  • 16
  • 22
3
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

modern variant :)

Black
  • 5,023
  • 6
  • 63
  • 92
Konstantin Salavatov
  • 4,370
  • 2
  • 25
  • 24
1

Another method

 string customHeader = string.Empty;
        if (Request.Headers.TryGetValue("x-date", out var xdateValue))
        {
            customHeader = xdateValue;
        };
rajquest
  • 535
  • 1
  • 5
  • 10
0

This may sound obvious, but make sure the Controller where you are reading the headers in, is the first Controller where the request goes through.

I had two WebAPI projects communicating with each other. The first one was a proxy, the second contained the logic. Silly me, I tried reading the custom headers in the second Controller, instead of the first one.

tno2007
  • 1,993
  • 25
  • 16
0
var token = string.Empty;
if (Request.Headers.TryGetValue("MyKey",  out headerValues))
{
    token = headerValues.FirstOrDefault();
}
janw
  • 8,758
  • 11
  • 40
  • 62
0
var headers = Request.Headers;
string token = headers.Contains("token") ? headers.GetValues("token").FirstOrDefault() ?? "" : "";
O Thạnh Ldt
  • 1,103
  • 10
  • 11