September 2010 - Posts

Enumerating Through XML Elements Using LINQ-to-XML
28 September 10 09:25 PM | Scott Mitchell | 1 comment(s)

4Guys reader Dan D. recently emailed me with an inquiry surrounding my article series, Building a Store Locator ASP.NET Application Using Google Maps API, specifically on how to access a different set of XML elements within the XML data returned from the Google Maps API’s geocoding service. Google’s geocoding service is offered as a URL that, when requested, returns information about a particular address. For instance, if you point your browser to http://maps.google.com/maps/api/geocode/xml?address=1600+Pennsylvania+Ave,+Washington+D.C.&sensor=false you should see an XML response that indicates whether the address is valid, the formatted address, the components that make up the address, and geographical information about the address, including the latitude and longitude coordinates.

This geocoding service is used by the Store Locator application in two ways:

  1. To validate the user-entered address. If the user enters an ambiguous address, like Springfield, then the geocoding service will return possible matches. These are displayed to the user, allowing her to choose which address she meant.
  2. To determine the latitude and longitude coordinates of the user-entered address. These coordinates are used to retrieve those stores that are nearby.

The Store Location application includes a method named GetGeocodingSearchResults that, when called, makes an HTTP request to the geocoding service and returns the results as an XElement object, one of the key components of LINQ-to-XML.

Dan’s question follows:

I have a question with regards to accessing the elements contained within the address_components[] array.  Specifically, I would like to return the long and short names for locality and country.  I was wondering if you could post an small article on how to iterate through the XML array components loaded into the XElement.

The address_components[] array Dan refers to is the set of <address_component> elements returned by the geocoding service. Again, visit http://maps.google.com/maps/api/geocode/xml?address=1600+Pennsylvania+Ave,+Washington+D.C.&sensor=false. Note how there are multiple <address_component> elements detailing the type and long and short names for each component of the address. For the address 1600 Pennsylvania Ave, Washington D.C. there are the following address components:

<result>
  <address_component>
   <long_name>1600</long_name>
   <short_name>1600</short_name>
   <type>street_number</type>
  </address_component>
  <address_component>
   <long_name>Pennsylvania Ave NW</long_name>
   <short_name>Pennsylvania Ave NW</short_name>
   <type>route</type>
  </address_component>
  <address_component>
   <long_name>Washington</long_name>
   <short_name>Washington</short_name>
   <type>locality</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>Washington</long_name>
   <short_name>Washington</short_name>
   <type>administrative_area_level_3</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>District of Columbia</long_name>
   <short_name>District of Columbia</short_name>
   <type>administrative_area_level_2</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>District of Columbia</long_name>
   <short_name>DC</short_name>
   <type>administrative_area_level_1</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>United States</long_name>
   <short_name>US</short_name>
   <type>country</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>20500</long_name>
   <short_name>20500</short_name>
   <type>postal_code</type>
  </address_component>
  ...
</result>

Note that each <address_component> element has a <long_name> and <short_name> child element, and one or more <type> child elements.

To simple iterate through each <address_component> element we could use the following code:

var results = GoogleMapsAPIHelpersCS.GetGeocodingSearchResults(“...”);

var addressComponents = results.Element("result").Elements("address_component");
foreach (var component in addressComponents)
{
    var longName = component.Element("long_name").Value;
    var shortName = component.Element("short_name").Value;

    var types = new List<string>();
    foreach (var type in component.Elements("type"))
        types.Add(type.Value);

    // At this point you can do whatever it is you want to do 
    // with the longName, shortName, and types information for
    // this component...
    if (types.Contains("locality") || types.Contains("country"))
        Response.Write(string.Format("<p>LongName = {0}, ShortName = {1}, Types = {2}</p>",
                                    longName, 
                                    shortName, 
                                    string.Join(", ", types.ToArray())
                                )
                    );
}

Here, we reference the set of <address_component> elements using results.Element(“result”).Elements(“address_components”), where results is the XElement object returned from the GetGeocodingSearchResults method. The Element(“results”) call gets a reference to the <result> XML element, while Elements(“address_component”) gives us the enumerable collection of <address_component> elements, which we then can loop through.

Inside the loop we get the values of the <long_name> and <short_name> XML elements and then loop through the set of <type> elements, the value of each to a List of strings (types). Finally, we can do what Dan is interested in doing – determine if the address component is for the locality or country and, if so, do something with the long and short names. Here, I simply display them via a Response.Write statement.

Another option is to use LINQ to create an anonymous type that models the information of interest. The following statement creates a variable named addressComponents2 that is an enumeration of anonymous objects that have three properties: LongName, ShortName, and Types, which contain the values of the <long_name>, <short_name>, and <type> elements for each <address_component>.

var results = GoogleMapsAPIHelpersCS.GetGeocodingSearchResults(“...”);

var addressComponents2 =
        from component in results.Element("result").Elements("address_component")
        select new
        {
            LongName = component.Element("long_name").Value,
            ShortName = component.Element("short_name").Value,
            Types = (from type in component.Elements("type")
                        select type.Value).ToArray()
        };

We can now filter the results using the Where method:

var filteredAddressComponents = addressComponents2
                                    .Where(addr => addr.Types.Contains("locality") ||
                                                    addr.Types.Contains("country"));

And now enumerating over filteredAddressComponents returns just those address components for the locality or country types. The following loop walks through each of these and emits the LongName, ShortName, and Types property values. Note how these are actual properties and not strings, meaning we have strong typing, which brings with it the benefits of IntelliSense and compile-time support.

// At this point you can use a foreach loop to 
// walk through the various components
foreach (var addr in filteredAddressComponents)
{
    Response.Write(string.Format("<p>LongName = {0}, ShortName = {1}, Types = {2}</p>",
                                addr.LongName, 
                                addr.ShortName, 
                                string.Join(", ", addr.Types.ToArray())
                            )
                );
}

Happy Programming!

Filed under:
Use jQuery to Open “External” URLs in a New Browser Window
15 September 10 03:24 AM | Scott Mitchell | 4 comment(s)

As any web developer knows, the HTML anchor element (<a>), when used in the following form:

<a href="http://www.scottonwriting.net">Click Me!</a>

creates a hyperlink with the text “Click Me!” that, when clicked, whisks the user to the specified href value, in this case my blog, ScottOnWriting.NET. By default, clicking a link opens the specified URL in the user’s existing browser window; however, using the <a> tag’s target attribute it is possible to open the URL in a new window. Adding target=”_blank” to the <a> element will cause the browser to open the link in a new browser window:

<a target="_blank" href="http://www.scottonwriting.net">Click Me!</a>

Some websites like to have all links to “external” web pages open in a new browser window, while having “internal” links open in the same browser window. I use the words external and internal in quotes here because their definitions can depend on the website. Some websites would consider URL that specifies a hostname in the href to be “external” – such as http://www.4guysfromrolla.com/ScottMitchell – while URLs that lack a hostname would be “internal” – such as /sowblog/archive/2010/09.aspx. Other websites might want links to partner websites to still be considered “internal,” even though they include a hostname.

I recently worked on a project where the client wanted this kind of behavior. He had hundreds of existing web pages, each with dozens of links, all of which lacked a target attribute. He didn’t want to have to go through the pages and links, one at a time, adding the target attribute where needed. To help address this problem I wrote a very simple jQuery plugin that can be used to automatically add a target attribute to “external” URLs.

WARNING: I know just enough JavaScript and jQuery to be dangerous, so please don’t presume my plugin is in any way an example of best practices. In fact, if you have any feedback or suggestions on how to improve it, please let me know in the comments!

The plugin defines a single function, UrlTarget([whiteList], [targetName]). The following line of code (which you’d place in $(document).ready, presumably) will add a target=”_blank” attribute to all “external” links. Without specifying a whiteList, all URLs that start with http:// or https:// are considered external, whereas all that don’t are considered internal:

$("a").UrlTarget();

If you want certain hostnames to be considered “internal,” simply specify one or more regular expressions in an array as the whiteList. If the hostname for a hyperlink matches on any of the regular expressions then it is considered “internal” and the target attribute is not added. For instance, to have all URLs that point to 4GuysFromRolla.com or ScottOnWriting.NET considered “internal,” you’d specify the following whiteList value:

$("a").UrlTarget([
    '^(www\.)?4guysfromrolla\.com$',
    '^(www\.)?scottonwriting\.net$'
]);

If you specify a targetName value, the target attribute added to external URLs is assigned that targetName. If this input parameter is omitted then the target value “_blank” is used. Also, note that if a hyperlink with an external URL already has its target attribute set then it is not overwritten by UrlTarget. Likewise, if a hyperlink with an internal URL has a target attribute set, it is not removed.

To use my plugin you’ll need to download the script at http://scottonwriting.net/sowblog/CodeProjectFiles/urlTarget.js, save it to your website, and then reference it via a <script> tag. I’ve got a demo online available at http://scottonwriting.net/sowblog/CodeProjectFiles/JQueryLinksDemo.htm, which has the following JavaScript:

<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="urlTarget.js"></script>

<script type="text/javascript">
        $(document).ready(function() {
            $("a").UrlTarget([
                '^(www\.)?4guysfromrolla\.com$',
                '^(www\.)?scottonwriting\.net$'
            ]);
        });
</script>

Note that both the jQuery and urlTarget.js libraries must be referenced.

Happy Programming!

Filed under:
Adding a RESTful Service to My Boggle Solver
11 September 10 02:47 AM | Scott Mitchell | 5 comment(s)

This blog post has been deprecated. Please see Updating My Online Boggle Solver Using jQuery Templates and WCF for an updated discussion on the solver service, the data it returns, and how to call it from a web page.

My immediate and extended family enjoys playing games, and one of our favorites is Boggle. Boggle is a word game trademarked by Parker Brothers and Hasbro that involves several players trying to find as many words as they can in a 4x4 grid of letters. At the end of the game, players compare the words they found. During this comparison I've always wondered what words we may have missed. Was there some elusive 10-letter word that no one unearthed? Did we only discover 25 solutions when there were 200 or more?

To answer these questions I created a Boggle solver web application (back in 2008) that prompts a user for the letters in the Boggle board and then recursively explores the board to locate (and display) all available solutions. This Boggle solver is available online - fuzzylogicinc.net/Boggle. My family uses it every time we get together and play Boggle. For more information on how it works and to get your hands on the code, check out my article, Creating an Online Boggle Solver.

Recently, I’ve been working on some projects that have involved creating RESTful web services using WCF. Being Friday, I decided to have a little fun and add a RESTful interface to the Boggle solver. This was actually quite easy to do and took all of 5 minutes.

Creating the Boggle Solver Service

I started by adding a new item to my website of type WCF Service, naming it Solver.svc. This created three files:

  • Solver.scr
  • ISolver.cs
  • Solver.cs

In the contract (ISolver.cs) I added a single method, Solve, that accepts two inputs: a string representing the board and a string indicating the minimum number of letters for a word to be considered a solution. (Boggle rules allow for words of three letters or more, but house rules only count words that are four letters or longer.) I then used the WebGet attribute to indicate that the board and length input parameters would be specified via the querystring fields BoardID and Length, and that the resulting output should be formatted as JSON.

[ServiceContract]
public interface ISolver
{
    [OperationContract]
    [WebGet(UriTemplate = "?BoardID={board}&Length={length}", ResponseFormat=WebMessageFormat.Json)]
    BoggleWordDTO[] Solve(string board, string length);
}

Note that the Solve method returns an array of BoggleWordDTO objects. This is a new class I created to represent the data to transmit from the service. This class has two properties:

  • Word – a string value that represents a word found in the Boggle board, and
  • Score – the score for that solution. According to the official rules, three and four letter words are worth 1 point, five letter words are worth 2, six letter words worth 3, seven letter words worth 5, and eight letter words and longer worth 11.

The Solve method implementation (Solver.cs) is pretty straightforward. It starts with a bit of error checking to ensure that the passed in board and letter information is kosher. Next, it creates a BoggleBoard object, specifying the minimum number of letters for a solution and the board contents. Then the BoggleBoard object’s Solve method is invoked, which performs the recursion and computes the set of solutions (as an object of type BoggleWordList). The solutions are then converted into an array of BoggleWordDTO objects, which is then returned to the client.

public BoggleWordDTO[] Solve(string board, string length)
{
    ...

    // Create the BoggleBoard
    BoggleBoard bb = new BoggleBoard(
                        letters,
                        board[0].ToString(), ..., board[15].ToString()
                    );

    // Solve the Boggle board
    var solutions = bb.Solve();

    // Populate and return an array of BoggleWordDTO objects
    return solutions
                .Select(s => new BoggleWordDTO()
                {
                    Word = s.Word,
                        Score = s.Score
                })
                .ToArray();
}

Because the service is configured to return the data using JSON, the results are serialized into a JSON array.

In addition to creating the Solver-related files and writing the code I noted, I also had to add <system.serviceModel> configuration to Web.config to permit HTTP access to the service and to enable ASP.NET compatibility. The reason I had to enable ASP.NET compatibility is because the dictionary used by the solver is a text file stored on disk, and the solver gets the path to that text file using Server.MapPath (namely, HttpContext.Current.Server.MapPath(“…”)). Without ASP.NET compatibility, HttpContext.Current is null when the service attempts to solve and then the call to Server.MapPath blows up. Also, I had to specify the Factory attribute in the <%@ ServiceHost %> directive of the Solver.svc file.

[UPDATE: 2010-09-10] Ben Amada posted a helpful comment pointing me to the existence of the HostingEnvironment.MapPath method, which does the same work as Server.MapPath but doesn’t require an HttpContext object. I updated this code accordingly. I also updated the code that cached the dictionary in memory, replacing the use of HttpContext.Current.Cache with HttpRuntime.Cache, which I probably should have been using all along. The code has been updated. Thanks, Ben!

Using the Boggle Solver Service

To use the service, just point your browser (or your code/script) to:  http://fuzzylogicinc.net/Boggle/Solver.svc?BoardID=board&Length=length. The board value should be entered as a string of the characters in the Boggle board, starting from the upper left corner and reading to the right and down. For example, if you had the board:

r e i b
t m f w
i r a e
r h s t 

You would use a board value of reibtmfwiraerhst. letter should be a number between 3 and 6, inclusive.

So, to find all solutions to the above board that are four or more letters, you’d visit: http://fuzzylogicinc.net/Boggle/Solver.svc?BoardID=reibtmfwiraerhst&Length=4

Doing so would return the following (abbreviated) JSON:

[{"Score":1,"Word":"amir"},{"Score":2,"Word":"amirs"},{"Score":1,"Word":"awes"},{"Score":1,"Word":"bier"},{"Score":1,"Word":"ears"},{"Score":1,"Word":"east"},...]

The above JSON represents an array of objects, where each object has two properties, Score and Word.

So how can this service be used? Well, with a bit of JavaScript you can call the service from a browser and display the results dynamically. I’ve included a rudimentary example in the download (which you can find at the end of this blog post) that prompts the user to enter the 16 characters for the board and the minimum number of letters. It then uses jQuery’s getJSON function to make a call to the service and get the data back. The JSON array is then enumerated and a series of list items are constructed, showing each solution in a bulleted list.

Here is the web page when you visit it and enter a boggle board and the minimum number of letters (but before you click the “Find All Words!” button.

BoggleBoard2

Clicking the “Find All Words!” button executes the following jQuery script:

$.getJSON(
    "Solver.svc",
    {
        "BoardID": $("#board").val(),
        "Length": $("#length").val()
    },
    function (words) {
        var output = "No solutions exists!";

        if (words.length > 0) {
            output = "<h2>" + words.length + " Solutions!</h2><ul>";

            var score = 0;

            $.each(words, function (index, word) {
                score += word.Score;
                output += "<li>" + word.Word + " (" + word.Score + " points)</li>";
            });

            output += "</ul><p>Total score = " + score + " points!</p>";
        }

        $("#solutions").html(output);
    }
);

Note that the above script calls the Solver.svc service passing in the BoardID and Length querystring parameters. The textbox where the user enters the board has an id of board while the minimum letter drop-down list has an id of length. The function defined in the call is what is executed when the result comes back successfully. Here, the jQuery each function is used to enumerate the array and build up a string of HTML in a variable named output that produces a bulleted list of solutions. The total number of solutions and total number of points is also included in output. Finally, the contents of output are dumped to a <div> on the page with an id of solutions.

Here’s the page after clicking the “Find All Words!” button. Nothing fancy, of course, and not nearly as useful or eye-pleasing as the website’s results page, but it does illustrate one way you can use the Boggle Solver.svc service.

BoggleBoard3

Download the Code!

You can download the complete Boggle solver engine, web application, and WCF RESTful service from http://aspnet.4guysfromrolla.com/code/BoggleSolver.zip.

Happy Programming, and Happy Boggling!

More Posts

Archives

My Books

  • Teach Yourself ASP.NET 4 in 24 Hours
  • Teach Yourself ASP.NET 3.5 in 24 Hours
  • Teach Yourself ASP.NET 2.0 in 24 Hours
  • ASP.NET Data Web Controls Kick Start
  • ASP.NET: Tips, Tutorials, and Code
  • Designing Active Server Pages
  • Teach Yourself Active Server Pages 3.0 in 21 Days

I am a Microsoft MVP for ASP.NET.

I am an ASPInsider.