patterncsharpModerate
Get params from a URL
Viewed 0 times
paramsfromgeturl
Problem
I want to write my own version of
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)?
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 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 result = new Dictionary();
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();
}Solution
I figure
Update
I went ahead and translated to C#:
Update 2
Here's a more functional C# version:
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):
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 IDictionaryUpdate
I went ahead and translated to C#:
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
...
static Dictionary GetParams(string uri)
{
var matches = Regex.Matches(uri, @"[\?&](([^&=]+)=([^&=#]*))", RegexOptions.Compiled);
var keyValues = new Dictionary(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 GetParams(string uri)
{
var matches = Regex.Matches(uri, @"[\?&](([^&=]+)=([^&=#]*))", RegexOptions.Compiled);
return matches.Cast().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","")]Code Snippets
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 IDictionaryusing 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;
}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)
);
}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","")]Context
StackExchange Code Review Q#1588, answer score: 16
Revisions (0)
No revisions yet.