I figure HttpUtility.ParseQueryString
is just incredibly robust, and you likely are missing something. But I can't spot anything in particular and it's likely that without deep research you won't be able to cover all the cases which were clearly studied and thought out in Microsoft's implementation. However, I think your implementation can be refactored to be easier to read and maintain by using regular expressions instead of brute force string splitting. I present the following in F# which hopefully you can translate easily enough to C# (the key functionality relies on the same .NET libraries available in both languages). I'm still new to regex writing myself, so I'm sure it's not as robust as it could be, but it seems to work well for all the typical cases I tested (generally assumes query string is not malformed, allows no query string, allows empty values but not empty keys, accounts for possible # delimiter after query string part):
open System
open System.Text.RegularExpressions
let uriParams uri =
let matches = Regex.Matches(uri, @"[\?&](([^&=]+)=([^&=#]*))", RegexOptions.Compiled)
seq { //create a sequence of key, value tuples extracted from the matches
for m in matches do
yield Uri.UnescapeDataString(m.Groups.[2].Value), Uri.UnescapeDataString(m.Groups.[3].Value)
} |> dict // convert the sequence of key, value tuples into an immutable IDictionary
Update
I went ahead and translated to C#:
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
...
static Dictionary<string, string> GetParams(string uri)
{
var matches = Regex.Matches(uri, @"[\?&](([^&=]+)=([^&=#]*))", RegexOptions.Compiled);
var keyValues = new Dictionary<string, string>(matches.Count);
foreach (Match m in matches)
keyValues.Add(Uri.UnescapeDataString(m.Groups[2].Value), Uri.UnescapeDataString(m.Groups[3].Value));
return keyValues;
}
Update 2
Here's a more functional C# version:
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
...
static Dictionary<string, string> GetParams(string uri)
{
var matches = Regex.Matches(uri, @"[\?&](([^&=]+)=([^&=#]*))", RegexOptions.Compiled);
return matches.Cast<Match>().ToDictionary(
m => Uri.UnescapeDataString(m.Groups[2].Value),
m => Uri.UnescapeDataString(m.Groups[3].Value)
);
}
Edit
I updated the regex to address the very important consideration of the # delimiter that @Visionary pointed out. That is probably the biggest thing missing from your own implementation functionality-wise. Also, here is a list of some cases I tested (using the F# version):
uriParams "http://www.foo.com" // []
uriParams "http://www.foo.com?" // []
uriParams "http://www.foo.com?q=" // [("q","")]
uriParams "http://www.foo.com?q=search&doit=no" // [("q","search"), ("doit","no")]
uriParams "http://www.foo.com?q=search&doit=no&go=further" // [("q","search"), ("doit","no"), ("go","further")]
uriParams "http://www.foo.com?q=&doit=no" // [("q",""), ("doit","no")]
uriParams "http://www.foo.com?q=&doit=no&=&test&good=true" //[("q",""), ("doit","no"), ("good","true")]
uriParams "http://www.foo.com?q=search&doit=no#validationError" // [("q","search"), ("doit","no")]
uriParams "http://www.foo.com?q=search&doit=no#validationError=true" // [("q","search"), ("doit","no")]
uriParams "http://www.foo.com?q=se%20arch&do%20it=no" // [("q","se arch"), ("do it","no")]
uriParams "http://www.foo.com?q=search&doit=#validationError" // [("q","search"), ("doit","")]
Uri.UnescapeDataString
. I had fun coming up with a regex solution for this, glad you liked it. Certainly go with what is easier for you to read and maintain. But I also encourage you to delve into regex land: looking back on string processing code I've written in the past, I can see how much time and effort I could have saved myself. Plus it's fun! \$\endgroup\$