10
\$\begingroup\$

I want to write my own version of HttpUtility.ParseQueryString, because it is not included in the .NET Framework Client Profile. My code is pretty straightforward and simple, but I'm looking at the code of HttpUtility.ParseQueryString with Reflector, and is about 200 lines including the code of the called methods.

I don't want to just steal the code specially because there are parts that I have no idea what they are for. Is my code OK or am I missing something (I don't care about performance here)?

Dictionary<string, string> GetParams(string str)
{
    int questionMarkIndex = str.IndexOf('?');
    if (questionMarkIndex != -1)
    {
        string paramsString = str.Substring(questionMarkIndex + 1);
        string[] keyValues = paramsString.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
        Dictionary<string, string> result = new Dictionary<string, string>();

        foreach (string keyValue in keyValues)
        {
            string[] splitKeyValue = keyValue.Split(new char[] { '=' });
            if (splitKeyValue.Length == 2 && !string.IsNullOrWhiteSpace(splitKeyValue[0]))
            {
                string unencodedKey = Uri.UnescapeDataString(splitKeyValue[0]);
                string unencodedValue = Uri.UnescapeDataString(splitKeyValue[1]);
                result[unencodedKey] = unencodedValue;
            }
        }

        return result;
    }
    else return new Dictionary<string, string>();
}
\$\endgroup\$
  • \$\begingroup\$ Should I assume my code is OK from not getting any answer? \$\endgroup\$ – Juan Mar 31 '11 at 20:24
  • \$\begingroup\$ Good catch with with the + symbol, I've run into that same issue in the Java libraries: I wonder if that can be considered a bug in 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\$ – Stephen Swensen Apr 2 '11 at 6:22
14
\$\begingroup\$

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","")]
\$\endgroup\$
  • \$\begingroup\$ Nice implementation. But don't forget RFC3986: # may also be a delimiter tools.ietf.org/html/rfc3986 \$\endgroup\$ – Visionary Software Solutions Apr 1 '11 at 4:34
  • 1
    \$\begingroup\$ Thanks, i joined this community just to thank you! The Uri parse query string was not working for me. You can always depend on RegEx \$\endgroup\$ – Radmation Jun 27 '16 at 19:27
  • \$\begingroup\$ Awesome @Radmation, glad to help! \$\endgroup\$ – Stephen Swensen Jun 30 '16 at 1:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.