Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

WebAPI: Getting Headers, QueryString and Cookie Values


:P
On this page:

Say what you will about how nice WebAPI is, but a few things in the internal APIs are not exactly clean to use. If you decide you want to access a few of the simple Request collections like Headers, QueryStrings or Cookies, you'll find some pretty inane and inconsistent APIs to retrieve values from them. It's not anywhere as easy as ASP.NET's simple Request.Headers[]/QueryString[]/Cookies[] collections. Instead you have to wade through various different implementations  of nested IEnumerable collections which are used to return collections - presumably for multiple values which is the .0005% use case. Each one of these collections need to be accessed differently and not exactly in the way you'd expect from any other Web platform tool.

The syntax to use them is definitely on the verbose side and for me it always throws me for a few minutes on how to best dig into these collections to retrieve a single value. I hate utility code that stops me in my tracks like that, especially for something that should be so trivial.

I finally got tired of trying to remember how to exactly retrieve values from these collections. So, I finally broke down and added a few extension methods that make this job a little simpler using a few one liners.

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

namespace System.Web.Http
{

    /// <summary>
    /// Extends the HttpRequestMessage collection
    /// </summary>
    public static class HttpRequestMessageExtensions
    {

        /// <summary>
        /// Returns a dictionary of QueryStrings that's easier to work with 
        /// than GetQueryNameValuePairs KevValuePairs collection.
        /// 
        /// If you need to pull a few single values use GetQueryString instead.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static Dictionary<string, string> GetQueryStrings(this HttpRequestMessage request)
        {
            return request.GetQueryNameValuePairs()
                          .ToDictionary(kv => kv.Key, kv=> kv.Value, StringComparer.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Returns an individual querystring value
        /// </summary>
        /// <param name="request"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GetQueryString(this HttpRequestMessage request, string key)
        {      
            // IEnumerable<KeyValuePair<string,string>> - right!
            var queryStrings = request.GetQueryNameValuePairs();
            if (queryStrings == null)
                return null;

            var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, key, true) == 0);
            if (string.IsNullOrEmpty(match.Value))
                return null;

            return match.Value;
        }

        /// <summary>
        /// Returns an individual HTTP Header value
        /// </summary>
        /// <param name="request"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        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();
        }

        /// <summary>
        /// Retrieves an individual cookie from the cookies collection
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cookieName"></param>
        /// <returns></returns>
        public static string GetCookie(this HttpRequestMessage request, string cookieName)
        {
            CookieHeaderValue cookie = request.Headers.GetCookies(cookieName).FirstOrDefault();
            if (cookie != null)
                return cookie[cookieName].Value;

            return null;
        }

    }
}

All methods return null if the key value is not found and only a single value is returned (the 99.99995% case).

Now I can see that for efficiency it might be better to read say the query string collection once and read several values out at once rather than re-reading the collection each time. But still this is something that WebAPI should handle internally. At the very least the internal representations of these collections should access in a similar fashion instead of returning crazy shit like IEnumerable<KeyValuePair<string,string>>.

Anyway, I hope this saves some of you some brain cycles - I know it will for me.

Posted in Web Api  

The Voices of Reason


 

Ross Smith
April 16, 2013

# re: WebAPI: Getting Headers, QueryString and Cookie Values

Spot on Rick, this has annoyed me too

IEnumerable<KeyValuePair<string,string>> - genius

Rick Strahl
April 16, 2013

# re: WebAPI: Getting Headers, QueryString and Cookie Values

@Ross - I'm sure there's some reason to this - most likely it's more efficient than a dictionary and can be streamed. Still it's silly to have to futz around with a clunky interface like this. I think nobody looks and tht and goes right on - it'll throw anybody into a 'WTF do I do with this?' for a few cycles at least.

Richard
April 16, 2013

# re: WebAPI: Getting Headers, QueryString and Cookie Values

If request.GetQueryNameValuePairs() can return null, as implied by your GetQueryString method, shouldn't your GetQueryStrings method check for that?

Thiru
March 26, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

can it be any issue if we use HttpContext.Current.Request.QueryString["val1"]

Rick Strahl
March 27, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

@Thiru - that only works hosted inside of ASP.NET. If you self-host, there's no HttpContext and your code will break.

Sean
May 02, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

It's a small thing, but in GetHeader() the variable "keys" should really be called "values".

me
July 25, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

Thanks for posting this! It is definitely a time-saver and I agree, the API syntax is much more annoying than it used to be for these common requests.

Will
October 24, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

return request.GetQueryNameValuePairs()
                        .ToDictionary(kv => kv.Key, kv=> kv.Value, StringComparer.OrdinalIgnoreCase);


This will throw an exception for duplicate key, if a query parameter is repeated.

shuo
December 26, 2014

# re: WebAPI: Getting Headers, QueryString and Cookie Values

Thanks for sharing this, it really take me more than a few cycles to search online for a clean code. The API is not at all intuitive.

Shawn Kovac
January 28, 2016

# re: WebAPI: Getting Headers, QueryString and Cookie Values

as Will pointed out, a fundamental flaw in your code is that duplicate keys like 'key=1&key=2&key=3' will break your code. that's because IDictionaries (Dictionary and SortedList) are slightly different than what you'd expect for QueryStrings and Form values and cookie values. in all these, a duplicate key shud give a result that resembles 'key=1,2,3'. This is why .NET has a NameValueCollection. In terms of efficiency and speed, the NameValueCollection uses an internal hash table, so it's lookup speed is as fast as Dictionary<string, string>. But it terms of duplicate keys, the NameValueCollection works as it shud with duplicate keys, where the values are combined. This is also why Request.QueryString is a NameValueCollection and *not* a Dictionary<string, string> nor a SortedList<string, string>. Happy Coding!

DictionaryMan
March 01, 2016

# re: WebAPI: Getting Headers, QueryString and Cookie Values

NameValueCollection lookup speed is nowhere near the lookup speed of Dictionary<string, string>

From http://www.dotnetperls.com/namevaluecollection

NameValueCollection lookup: 2768 ms
Dictionary lookup: 407 ms

Mesca
September 23, 2016

# re: WebAPI: Getting Headers, QueryString and Cookie Values

in addition to get all headers list

        private static Dictionary<string, string> getHeaders(this HttpRequestMessage request)
        {
            return request.Headers.ToDictionary(
                kv => kv.Key,
                kv => kv.Value != null ? string.Join("", kv.Value) : "",
                StringComparer.OrdinalIgnoreCase
                );
        }
.

Nex
October 09, 2016

# re: WebAPI: Getting Headers, QueryString and Cookie Values

Can you not use
request.requesturi.query
instead?

Alan
January 23, 2018

# re: WebAPI: Getting Headers, QueryString and Cookie Values

Using: kv ⇒ kv.Key.Equals(key, StringComparison.OrdinalIgnoreCase)

instead of: kv ⇒ string.Compare(kv.Key, key, true) == 0

could shave a millisecond or two


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024