Windows 8 Developer Preview - WCF, Bing, GeoLocator and FlipView

27 September 2011 (updated January 24, 2012)

After playing around with a few "Hello Metro World" type apps, I decided to try and create something a little more ambitious. I wanted to create a Windows 8 Metro-style app that:

  • Discovers your location and displays a list of images relevant to that location
  • Uses a web service via an asynchronous pattern
  • Uses the Bing web service API to get the images
  • Uses the WinRT GeoLocator class
  • Uses the WinRT FlipView control to display the list of images

Here's a screenshot from the completed app:

The source for the app can be downloaded from here.

Code Walkthrough

The following steps describe the creation of the LocateMe app.

Note that the app was developed using the Windows 8 Developer Preview and Visual Studio 11 Express. Note also that I don't claim this is the right way to develop such an app - it's just a learning excercise for me!

  • In Visual Studio 11, create a new Project using the Application template (here I'm using C#, but you can use C#, C++, VB and JavaScript to accomplish the exact same thing):
  • Open MainPage.xaml, set the page title using a TextBlock, and then add two Buttons and a FlipView control:

    <UserControl x:Class="LocateMe.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="768" d:DesignWidth="1366">
    
        <Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
            <TextBlock x:Name="PageTitle" FontSize="60" Text="LocateMe" Margin="120,5,8,679" TextAlignment="Left" />
    
            <Grid HorizontalAlignment="Left" Height="542" Margin="120,93,0,0" VerticalAlignment="Top" Width="1238">
    
                <TextBlock     
                    x:Name="textBlockGeoResults" 
                    HorizontalAlignment="Left" 
                    Height="36" 
                    Margin="203,8,0,0" 
                    TextWrapping="Wrap" 
                    Text=""  
                    VerticalAlignment="Top" 
                    Width="270" 
                    FontSize="30"/>
    
                <Button     
                    x:Name="getGeoInfo" 
                    Content="Locate Me" 
                    HorizontalAlignment="Left" 
                    Height="26" 
                    Margin="0,8,0,0" 
                    VerticalAlignment="Top" 
                    Width="194" 
                    Tapped="GetGeoInfo_Tapped"/>
    
                <Button     
                    x:Name="getBingInfo"  
                    Content="Get Bing Images" 
                    HorizontalAlignment="Left" 
                    Height="26" 
                    Margin="0,48,0,0" 
                    VerticalAlignment="Top" 
                    Width="194" 
                    Tapped="GetBingInfo_Tapped"/>
    
                <TextBlock     
                    x:Name="textBlockBingResults" 
                    HorizontalAlignment="Left" 
                    Height="33" 
                    Margin="203,48,0,0" 
                    TextWrapping="Wrap" 
                    Text=""  
                    VerticalAlignment="Top" 
                    Width="270" 
                    FontSize="30"/>
    
                <FlipView     
                    x:Name="flipViewBing" 
                    BorderBrush="White" 
                    HorizontalAlignment="Left" 
                    Height="450" 
                    Width="650" 
                    VerticalAlignment="Bottom" >
    
                    <FlipView.ItemTemplate>
                        <DataTemplate>
                            <Image Source="{Binding}" Stretch="Uniform" />
                        </DataTemplate>
                    </FlipView.ItemTemplate>
    
                </FlipView>
            </Grid>
        </Grid>
    </UserControl>
  • Notice that the left margin of the page is always 120 pixels (at least it seems that's the Microsoft standard)
  • Open MainPage.xaml.cs and add two using statement to the defaults:
  • using Windows.Devices.Geolocation;
    using System.Collections.ObjectModel;
    

  • Before you can use the Bing API, you need to get yourself a (free) Bing Developer AppID.
    Once you have the AppID, right-click Service References in Solution Explorer and select Add Service Reference:
  • The URL of the Bing web service will be:
    http://api.bing.net/search.wsdl?AppID=YOUR_APPID&Version=2.2
  • Add the following code:
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Foundation;
    using Windows.Graphics.Display;
    using Windows.UI.ViewManagement;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    
    using Windows.Devices.Geolocation;
    using System.Collections.ObjectModel;
    
    namespace LocateMe
    {
        partial class MainPage
        {
            private ObservableCollection<string> m_bingImages;
            private Geolocator m_geo;
            private Geoposition m_pos = null;
    
            public MainPage()
            {
                InitializeComponent();
    
                m_bingImages = new ObservableCollection<string>();
                m_geo = new Geolocator();
            }
    
            private void GetGeoInfo_Tapped(object sender, Windows.UI.Xaml.Input.TappedEventArgs e)
            {
                GetGeoInfoFromDevice();
            }
    
            async private void GetGeoInfoFromDevice()
            {
                m_pos = await m_geo.GetGeopositionAsync();
                textBlockGeoResults.Text = string.Format("You are in {0}", m_pos.CivicAddress.Country);
            }
    
            private void GetBingInfo_Tapped(object sender, Windows.UI.Xaml.Input.TappedEventArgs e)
            {
                if (m_pos == null)
                {
                    textBlockGeoResults.Text = "Click 'Locate Me' first";
                    return;
                }
    
                try
                {
                    BingService.BingPortTypeClient bingProxy = new BingService.BingPortTypeClient();
                    BingService.SearchRequest1 request = BuildBingRequest();
                    Task<BingService.SearchResponse1> bingResults = bingProxy.SearchAsync(request);
                    DisplayBingResults(bingResults.Result.parameters);
                }
                catch (Exception ex)
                {
                    textBlockGeoResults.Text = ex.Message;
                }
            }
    
            private BingService.SearchRequest1 BuildBingRequest()
            {
                // Note that the generated proxy code needs to be modified as follows:
                /*
                    public System.Threading.Tasks.Task<LocateMe.BingService.SearchResponse1> 
                      SearchAsync(LocateMe.BingService.SearchRequest1 request) {
                        return base.Channel.SearchAsync(request);
                    }
                 * */
    
                BingService.SearchRequest1 request = new BingService.SearchRequest1();
                request.parameters = new BingService.SearchRequest();
    
                request.parameters.AppId = "YOUR_APPID_GOES_HERE";
                request.parameters.Query = "Country " + m_pos.CivicAddress.Country;
                request.parameters.Sources = new BingService.SourceType[] { BingService.SourceType.Image };
    
                request.parameters.Version = "2.2";
                request.parameters.Market = "en-us";
                request.parameters.Adult = BingService.AdultOption.Strict;
                request.parameters.AdultSpecified = true;
    
                request.parameters.Image = new BingService.ImageRequest();
                request.parameters.Image.Count = 10;
                request.parameters.Image.CountSpecified = true;
                request.parameters.Image.Offset = 0;
                request.parameters.Image.OffsetSpecified = true;
    
                return request;
            }
    
            private void DisplayBingResults(BingService.SearchResponse results)
            {
                textBlockBingResults.Text = results.Image.Results.Length.ToString() + 
                  " images returned from Bing";
                m_bingImages.Clear();
                
                //img.Thumbnail.Url or img.MediaUrl
                foreach (BingService.ImageResult img in results.Image.Results)  
                    m_bingImages.Add(img.MediaUrl);
    
                flipViewBing.ItemsSource = m_bingImages;
            }
        }
    }
    

  • The main points to note are:
    • The code added by Add Service Reference contains a bug (this is an early pre-Beta after all).

      Open the Reference.cs file (in Solution Explorer click Show All Files, then open the Service References → BingService → Reference.svcmap nodes to locate the Reference.cs source file) and find the SearchAsync method definition. Update its signature as indicated in the code comment above
    • Notice how easy it is to get a location (see GetGeoInfoFromDevice()). This works even when the machine/device doesn't have built-in GPS (it makes use of an IP-based geo-location service). You can also see that I'm using the GetGeopositionAsync() method of the Geolocator class. Async is now the default behaviour for all Metro operations likely to take 50ms or more.

      To make working with async operations as easy as working synchronously, Microsoft have introduced the async/wait pattern. The method itself is declared to be async, then you call the required method with an await statement:

      async private void GetGeoInfoFromDevice()
      {
          m_pos = await m_geo.GetGeopositionAsync();
          textBlockGeoResults.Text = string.Format("You are in {0}", 
              m_pos.CivicAddress.Country);
      }

      The call to GetGeopositionAsync() looks like it's a synchronous call, but at that point a new thread is created to handle the wait for the async call to complete (while the UI continues on a single thread). This pattern is used consistently throughout Metro-style programing.
    • The call to the Bing web service is also handled asynchronously, this time using the Task<T> pattern:

      Task<BingService.SearchResponse1> bingResults = bingProxy.SearchAsync(request);

      It's interesting to note that ONLY async methods are created by using Add Service Reference.
    • This is an interesting one. Apparently, XML-based declarative configuration is not supported in Windows 8 (or at least not by Metro apps). All configuration is code-based. I don't know if this is something that will chance in the future, but to quote Piyush Joshi, who is a Microsoft Program Manager on the WCF team:

      "...note that there is no configuration file generated. Metro apps do not support the traditional xml styled application so we have modified our Add Service Reference (ASR) experience from within a metro app to generate code based configuration."

      This is quite a big departure from previous best practices. When I asked about why the change was necessary, Piyush replied:

      Config's purpose is typically to enable changing application settings/ other parameters without recompiling. However for metro styled applications, once a developer has developed an application and crated a package and published it to marketplace, it is unlikely that customers who download and install this metro app package will want to do anything via xml configuration

      I agree that end-users would not be modifying the configuration, but that's not the point. XML-based configuration is to help the development and deployment of solutions. Currently, a single build can be re-configured by developers or network admins for development, staging, testing and production environments. If only code-based config is allowed, this means that a new build (a re-compile) would be necessary for each environment. Not a disaster, but a backwards step in terms of ease of administration and deployment. Hopefully this will be re-considered before the release of Windows 8.
    • The images for the FlipView control are added very easily by filling an ObservableCollection with strings (which are URLs). The <DataTemplate> contains an <Image> control which has its Source property set to specifiy data binding:

      <FlipView.ItemTemplate>
          <DataTemplate>
              <Image Source="{Binding}" Stretch="Uniform" />
          </DataTemplate>
      </FlipView.ItemTemplate>

      We setup the binding by setting the FlipView's ItemsSource property to point to the collection of image URLs:

      flipViewBing.ItemsSource = m_bingImages;

  • Before attempting to run the app your need to modify the Package App Manifest.
    Open the Package.appmanifest file and select the Capabilities tab.

    Make sure both Internet (Client) and Location capabilities are selected. This requests permission to use the specified features and devices, although the user will get a chance to allow/deny your app permission to use the, when it first runs.
  • Compile and run the app - that's it!