How to extract custom header value in Web API message handler?
Asked Answered
L

13

184

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?

Lordly answered 19/2, 2013 at 21:8 Comment(2)
what happens if you're using request.Headers.Get("MyCustomID"); ?Jocasta
There is no Get' on the HttpRequestHeaders` type. The message: "Cannot resolve symbol 'Get'" is produced.Lordly
A
300

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.

Aurelie answered 19/2, 2013 at 21:14 Comment(8)
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).Nano
@Drew request.Headers.Single(h => h.Key == "Authorization"); Much less code doing the same!Lepidote
Why not just var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();Irradiant
@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.Ralline
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.Ingrown
I got it using Request class instead of request, the rest should be the same.Pitchfork
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?Kary
TryGetValues doesn't work with custom headers?Grubb
M
51

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();
     }           
Margerymarget answered 3/9, 2014 at 9:1 Comment(0)
T
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.

Tipsy answered 24/7, 2013 at 22:25 Comment(6)
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.Tipsy
@Tipsy (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.Whiles
@Tipsy 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?Miscible
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.Tipsy
@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 methodLongship
@dotnetguy, you're right, it was, along the lines of Request = new HttpRequestWrapper(HttpContext.Current.Request);. See #3261434 for more.Tipsy
O
14

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();
        }
Orrery answered 5/4, 2016 at 17:39 Comment(2)
What if MyCustomID is not part of request.. it returns null exception.Obscene
@PrasadKanaparthi, TryGetValues are safeAphorize
W
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");
Wax answered 17/2, 2016 at 15:50 Comment(3)
Looks great, but GetFirstHeaderValueOrDefault has two parameters, so it complains about missing param when calling as the sample usage var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue"); Am I missing something?Harebrained
Added the new static class, replaced Response for Request. Called from API controller, as var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID"); got There is no argument given that corresponds to the required formal parameter 'headerKey' of 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<T>(HttpRequestMessage, string)'Harebrained
@Harebrained are you declaring using HttpResponseMessageExtensions on the file you're attempting to use this extension?Wax
M
10

One line solution (assuming that the header exists)

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Mabel answered 22/6, 2018 at 23:10 Comment(5)
What if MyCustomID is not part of request.. it returns null exception.Obscene
@PrasadKanaparthi then you should use another answer like https://mcmap.net/q/135653/-how-to-extract-custom-header-value-in-web-api-message-handler. 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 ownMabel
Don't you agree that the code is working and can return header value?Mabel
It works fine.. if "MyCustomID" is part of request of request. Yes, all validation need to taken careObscene
@PrasadKanaparthi, if the header doesn't exist you will get an InvalidOperationException, not nullAphorize
L
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}");
            ...
        }
    }
Learned answered 5/11, 2017 at 9:17 Comment(0)
G
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.

Grouchy answered 24/5, 2017 at 8:46 Comment(0)
F
3
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

modern variant :)

Felicitasfelicitate answered 2/6, 2017 at 6:42 Comment(1)
What if MyCustomID is not part of request.. it returns null exception.Obscene
P
1

Another method

 string customHeader = string.Empty;
        if (Request.Headers.TryGetValue("x-date", out var xdateValue))
        {
            customHeader = xdateValue;
        };
Pongee answered 15/10, 2021 at 18:55 Comment(0)
P
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.

Primateship answered 16/9, 2020 at 13:4 Comment(0)
N
0
var token = string.Empty;
if (Request.Headers.TryGetValue("MyKey",  out headerValues))
{
    token = headerValues.FirstOrDefault();
}
Novitiate answered 3/12, 2020 at 6:36 Comment(0)
W
0
var headers = Request.Headers;
string token = headers.Contains("token") ? headers.GetValues("token").FirstOrDefault() ?? "" : "";
Waligore answered 7/11, 2022 at 4:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.