Loving ToDictionary

I love ToDictionary()! It allows me to easily transform disparate data into a common format I can use to send to methods making those methods less coupled and more readily reusable. Let me provide an example.

I have a method that needs to process header values of web request and do certain things based on their values, the signature looks something like this:

public ComService  GetServiceBasedOnSource(IServiceFactory factory, 
                                           HttpRequestHeaders headers, 
                                           string tokenValue)

I was given a requirement to have the same processing occur based of off query string values if headers are not available. I was passing headers directly to my method, however, so wasn’t sure what to do. I figured I could pull out common code and have two methods, one with signature for headers and one with signature for query string parameter values, which is turns out are a collection of KeyValuePair if you use the Request.GetQueryNameValuePairs() method. When thinking about this I realized if I pulled the common code out I’d have to transform the different input into something I could process, so if I was going to alter data why not let caller do it and have one method with a signature that wasn’t dependent on headers or keyvaluepairs?

This is where ToDictionary makes life easy. I changed my signature to take a dictionary instead of headers or keyvaluepairs and toDictionary makes this transformation a snap. My method now looks like:

public ComService  GetServiceBasedOnSource(IServiceFactory factory, 
                                           Dictionary<string, string> dictionary, 
                                           string tokenValue)

And to use it I just ToDictionary the inputs:

public ComService  GetServiceBasedOnSource(factory, 
                                           Request.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault()),
                                           token);

or 

public ComService  GetServiceBasedOnSource(factory, 
                                           Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value), 
                                           token);

RegEx For Parsing

Recently had a situation where we needed to parse city, state and zip code from an input string that could be in three or four different formats. I was provided with some legacy code that, supposedly, handled most of the cases. The code was in vb.net, already not making me happy, and several hundred lines long.   The code was not accessible to me as a component, so on top of it being suspect I had to copy and paste it to work with it.

The code walked through it’s assumptions and if they weren’t meant it would give back wrong results. It turned out it only handled one input case as well.
Here is just a taste of what I had to work with:

[vb]
Private Sub ParsePlace(ByVal sPlace As String, ByVal data As Location)
Dim sCity As String = String.Empty
Dim sState As String = String.Empty
Dim sCounty As String = String.Empty
Dim iIndex As Integer = 0

iIndex = sPlace.IndexOf(“,”)

If (iIndex > 0) Then ‘ “1 NW Clifton, NJ, Pasaic”
sCity = Left(sPlace, iIndex) ‘ City is left part up to first comma (1 NW Clifton)
sPlace = sPlace.Substring(iIndex + 1, sPlace.Length – iIndex – 1).Trim ‘ Place is from the 1st comma on (NJ, Pasaic)

If (sPlace.Length > 0) Then
iIndex = sPlace.IndexOf(“,”)

If (iIndex > 0) Then
sState = Left(sPlace, iIndex)
ElseIf sPlace.Length = 2 Then
sState = sPlace
End If

‘ We’re not using county yet, but here’s the code if we need it.
‘sPlace = sPlace.Substring(iIndex + 1, sPlace.Length – iIndex).Trim
‘If (sPlace.Length > 0) Then
‘ iIndex = sPlace.IndexOf(“,”, iIndex)
‘ sCounty = Left(sPlace, iIndex)
‘End If
End If
Else
[/vb]

There were many problems with this. First the code is painful to follow, this was just the start of many more lines of finding spaces and substringing. The second major problem is that it doesn’t handle multiple inputs, and making it do so would be painful.

To handle this we rewrote this using regular expressions. The benefit of the expressions, is that we can see if the input discreetly matches the pattern we think it should, and if it does pull off the pieces we want from the regex itself. This allows us to not have to write messy parsing code, and also to throw an error if we find a input format we don’t support as opposed to returning bad results.

The change looks like the below:

            string LocationExactMatch = @"^(0.0)\s+([\d]{5}|[\w]{6})\s+([\w|\s]+),\s*(\w{2})";
            string LocationDistanceTo = @"^(\d+.\d+)\s+\b(N|S|E|W|NE|NW|SE|SW|NNE|NNW|SSE|SSW)\b\s+([\d]{5}|[\w]{6})\s+([\w|\s]+),\s*(\w{2})";
            string LocationNoDistanceTo = @"^([\d]{5}|[\w]{6})\s+([\w|\s]+),\s*(\w{2})";
            string LocationNoZipCode = @"^(\d+.\d+)\s+\b(N|S|E|W|NE|NW|SE|SW|NNE|NNW|SSE|SSW)\b\s+([\w|\s]+),\s*(\w{2})";
            string LocationNoZipCodeExactMatch = @"^(0.0)\s+([\w|\s]+),\s*(\w{2})";

            var cityData = new CityData() { Comments = result };
            var match = Regex.Match(result, LocationDistanceTo);

            if ((match.Success))
            {
                cityData.MilesFromCity = Convert.ToDouble(match.Groups[1].Value);
                cityData.DirectionFromCity = match.Groups[2].Value;
                cityData.ZipCode = match.Groups[3].Value;
                cityData.CityName = match.Groups[4].Value;
                cityData.State = match.Groups[5].Value;
            }

            if(!match.Success &&  (match = Regex.Match(result, LocationExactMatch)).Success)
            {
                cityData.MilesFromCity = Convert.ToDouble(match.Groups[1].Value);
                cityData.ZipCode = match.Groups[2].Value;
                cityData.CityName = match.Groups[3].Value;
                cityData.State = match.Groups[4].Value;
            }

            if (!match.Success && (match = Regex.Match(result, LocationNoDistanceTo)).Success)
            {
                cityData.ZipCode = match.Groups[1].Value;
                cityData.CityName = match.Groups[2].Value;
                cityData.State = match.Groups[3].Value;
            }

            if (!match.Success && (match = Regex.Match(result, LocationNoZipCode)).Success)
            {
                cityData.MilesFromCity = Convert.ToDouble(match.Groups[1].Value);
                cityData.DirectionFromCity = match.Groups[2].Value;
                cityData.CityName = match.Groups[3].Value;
                cityData.State = match.Groups[4].Value;
            }

            if (!match.Success && (match = Regex.Match(result, LocationNoZipCodeExactMatch)).Success)
            {
                cityData.MilesFromCity = Convert.ToDouble(match.Groups[1].Value);
                cityData.CityName = match.Groups[2].Value;
                cityData.State = match.Groups[3].Value;
            }

            if (!match.Success)
            {
                throw new ArgumentException("Input Not Valid Format.");
            }

The above uses the ability of the regex to pull out values by group to do parsing, and errors if input does not match any of our expectations, much better in my book.