Is there a Portable Class Library (PCL) version Of HttpUtility.ParseQueryString contained in System.Web or some code I could use? I want to read a very complex URL.
-
What's wrong with referencing System.Web? – Ben Nov 28 '13 at 14:32
-
can you show us your complex URL – Indranil.Bharambe Nov 28 '13 at 14:35
-
5I can't reference System.Web in a Portable Class Library Project. – Muhammad Rehan Saeed Nov 28 '13 at 14:39
-
This is an example of a simpler URL I am trying to parse http://journeyplanner.tfl.gov.uk/user/XML_TRIP_REQUEST2?language=en&sessionID=0&itdDate=20130102&itdTime=0930&place_origin=London&type_origin=stop&name_origin=Leyton&place_destination=London&type_destination=stop&name_destination=Stratford&place_via=London&type_via=stop&name_via=Leytonstone&place_via=London&type_via=stop&name_via=Gants%20Hill&itOptionsActive=1&trITMOT=100&ptOptionsActive=1&imparedOptionsActive=1 – Muhammad Rehan Saeed Nov 28 '13 at 14:40
4 Answers
HttpUtility.ParseQueryString
returns HttpValueCollection
(Internal Class) which inherits from NameValueCollection
. NameValueCollection
is a collection of key value pairs like a dictionary but it supports duplicates, maintains order and only implements IEnumerable
(This collection is pre-generics). NameValueCollection
is not supported in PCL.
My solution (Partly lifted and modified from the .NET framework) is to substitute HttpValueCollection with Collection<HttpValue>
where HttpValue
is just a key value pair.
public sealed class HttpUtility
{
public static HttpValueCollection ParseQueryString(string query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if ((query.Length > 0) && (query[0] == '?'))
{
query = query.Substring(1);
}
return new HttpValueCollection(query, true);
}
}
public sealed class HttpValue
{
public HttpValue()
{
}
public HttpValue(string key, string value)
{
this.Key = key;
this.Value = value;
}
public string Key { get; set; }
public string Value { get; set; }
}
public class HttpValueCollection : Collection<HttpValue>
{
#region Constructors
public HttpValueCollection()
{
}
public HttpValueCollection(string query)
: this(query, true)
{
}
public HttpValueCollection(string query, bool urlencoded)
{
if (!string.IsNullOrEmpty(query))
{
this.FillFromString(query, urlencoded);
}
}
#endregion
#region Parameters
public string this[string key]
{
get { return this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value; }
set { this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value = value; }
}
#endregion
#region Public Methods
public void Add(string key, string value)
{
this.Add(new HttpValue(key, value));
}
public bool ContainsKey(string key)
{
return this.Any(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
}
public string[] GetValues(string key)
{
return this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).ToArray();
}
public void Remove(string key)
{
this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase))
.ToList()
.ForEach(x => this.Remove(x));
}
public override string ToString()
{
return this.ToString(true);
}
public virtual string ToString(bool urlencoded)
{
return this.ToString(urlencoded, null);
}
public virtual string ToString(bool urlencoded, IDictionary excludeKeys)
{
if (this.Count == 0)
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder();
foreach (HttpValue item in this)
{
string key = item.Key;
if ((excludeKeys == null) || !excludeKeys.Contains(key))
{
string value = item.Value;
if (urlencoded)
{
// If .NET 4.5 and above (Thanks @Paya)
key = WebUtility.UrlDecode(key);
// If .NET 4.0 use this instead.
// key = Uri.EscapeDataString(key);
}
if (stringBuilder.Length > 0)
{
stringBuilder.Append('&');
}
stringBuilder.Append((key != null) ? (key + "=") : string.Empty);
if ((value != null) && (value.Length > 0))
{
if (urlencoded)
{
value = Uri.EscapeDataString(value);
}
stringBuilder.Append(value);
}
}
}
return stringBuilder.ToString();
}
#endregion
#region Private Methods
private void FillFromString(string query, bool urlencoded)
{
int num = (query != null) ? query.Length : 0;
for (int i = 0; i < num; i++)
{
int startIndex = i;
int num4 = -1;
while (i < num)
{
char ch = query[i];
if (ch == '=')
{
if (num4 < 0)
{
num4 = i;
}
}
else if (ch == '&')
{
break;
}
i++;
}
string str = null;
string str2 = null;
if (num4 >= 0)
{
str = query.Substring(startIndex, num4 - startIndex);
str2 = query.Substring(num4 + 1, (i - num4) - 1);
}
else
{
str2 = query.Substring(startIndex, i - startIndex);
}
if (urlencoded)
{
this.Add(Uri.UnescapeDataString(str), Uri.UnescapeDataString(str2));
}
else
{
this.Add(str, str2);
}
if ((i == (num - 1)) && (query[i] == '&'))
{
this.Add(null, string.Empty);
}
}
}
#endregion
}
UPDATE
Updated so that HttpValueCollection now inherits from Collection rather than List as highlighted in the comments.
UPDATE 2
Updated to use WebUtility.UrlDecode if using .NET 4.5, thanks to @Paya.

- 35,627
- 39
- 202
- 311
-
1
-
1This almost works for me, but I am missing .ForEach in Remove(string key) – tofutim Mar 06 '15 at 19:20
-
var list = this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)) .ToList(); foreach(var x in list) { this.Remove(x); } – tofutim Mar 06 '15 at 19:24
-
1
You can also implement it like this:
public static class HttpUtility
{
public static Dictionary<string, string> ParseQueryString(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1); // +1 for skipping '?'
var pairs = query.Split('&');
return pairs
.Select(o => o.Split('='))
.Where(items => items.Count() == 2)
.ToDictionary(pair => Uri.UnescapeDataString(pair[0]),
pair => Uri.UnescapeDataString(pair[1]));
}
}
Here is a Unit test for that:
public class HttpParseQueryValuesTests
{
[TestCase("http://www.example.com", 0, "", "")]
[TestCase("http://www.example.com?query=value", 1, "query", "value")]
public void When_parsing_http_query_then_should_have_these_values(string uri, int expectedParamCount,
string expectedKey, string expectedValue)
{
var queryParams = HttpUtility.ParseQueryString(new Uri(uri));
queryParams.Count.Should().Be(expectedParamCount);
if (queryParams.Count > 0)
queryParams[expectedKey].Should().Be(expectedValue);
}
}

- 1,440
- 1
- 13
- 24
-
The disadvantage of this approach is that it does not allow arguments with duplicate keys. Also, it does not handle arguments will empty values. However, this will probably fit most peoples needs with more simplicity. – Muhammad Rehan Saeed Sep 15 '14 at 09:14
-
4There is nothing in the URL spec that stops the use of duplicate keys (See [this](https://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys) question). – Muhammad Rehan Saeed Apr 07 '16 at 09:56
My Flurl library is a PCL that parses query strings into IDictionary<string, object>
when you instantiate a Url
object from a string:
using Flurl;
var url = new Url("http://...");
// get values from url.QueryParams dictionary
The relevant parsing logic is here. Flurl is small, but feel free to swipe just those bits if you want.

- 37,557
- 17
- 150
- 173
I made a nuget package today that does basic query building and parsing. It's made for personal usage but available from the nuget.com repo. For personal usage means it may not be fully compliant with the 'http query specs'. Nuget link here
It's based on a dictionary so doesn't support duplicate keys, mainly because I don't know why you would want that... (can anyone enlighten me?)
It has 1 class representing a query that supports adding, getting parameters, checking if it contains a key... And a static method to parse a key and return a query instance.

- 396
- 3
- 10
-
1. Using a dictionary will cause duplicate key exceptions if you don't handle duplicate keys, this could cause your site to error. Hackers often try to get sites to error by passing random stuff, including duplicate keys. 2. Using a duplicate key is another way of accepting collections of things. Typically, you'd have to use comma separated values to achieve this. – Muhammad Rehan Saeed May 05 '16 at 08:21
-
I handle the exception a dictionary throws when adding a duplicate key. If this happens I replace the value with the new one, so the hacker part is not a problem here. I never knew that 2 was possible. Is this collection thing with duplicate keys a standardized way of doing that? If it is then I'll look into it. – Stan Wijckmans May 08 '16 at 17:46