Scope

In this article we will make a Windows Phone App, seeing how to deserialize informations coming from the Web in JSON format, to bind them to a WPF ListBox, so as to provide a simple interface through which the user can read the retrieved data. To this end we will use C#, plus the famous library Newtonsoft.Json, and a pinch of XAML concerning the graphical part. This article takes many of its parts from «Parse a JSON stream to show TechNet Medals on WPF ListBox», in which we’ve done the same thing for a desktop program, using Visual Basic.

Our goal

Nothing too complex, and at the same time something useful and fun - at least for TechNet users. We'll make an app which allows a TechNet and/or MSDN user to consult his profile using his/her smartphone, retrieving data from the Web and elaborating them in-app. In particular, we will focus on TechNet's medals, retrieving informations about a specific user's state.

Prerequisites

Before we begin to develop our project, we need to know two essential information, namely: 1) which is the address to query in order to retrieve the TechNet medals detail for a given user, and 2) how to uniquely identify our specific user. To this end, it is necessary to openin a browser the TechNet profile page for that user, and search - in its HTML code - for the script loadReputation().



In the picture you can see how the script works on a specific a URL. By copying this address in a new browser page, we see the following output serialized, which corresponds to the basic information of the user (Id, current points, etc.), and the medals details of the medals held at the moment, listed by type.



Now we know the URL from to query to retrieve the serialized information of interest, namely: https://api.recognition.microsoft.com/v1/user/<USER-ID>/detail?Locale=<LOCAL>, where - of course - the <USER-ID> and <LOCAL> tags must be replaced with the correct values: the first, must represent a valid user (and to know our ID or another user's, we must always search for it in the HTML code of the profile), while the second is the language in which we want our results to be retrieved (I will use the local "en-US" for the sake of translation).

The recovered data must be deserialized, i.e. it must be reduced to a more usable form - both from the point of view of the human reader, and for the ease of managing at developing stage - if compared to JSON format in which they are provided. To do this, as mentioned in the opening, we'll rely on the Newtonsoft.Json library, which provides quick tools and classes for serialization/deserialization purposes.
The library is available for download at 
https://json.codeplex.com/ but we will reference it through NuGet to avoid compiler errors (in particular, missing references to System.ComponentModel that could be raised in adding a manual reference).

At this point, we have all what we need to proceed in developing our program.


Referencing Newtonsoft.Json through Nuget


Starting Visual Studio, we must open Nuget Package Manager, using the function Manage NuGet Packages for Solution, like the following images shows:



It will be sufficient  to search and select for Json.NET package, then press on «Install» button.



After that, the namespace Newtonsoft.Json will become available in our project. From it, we will use the method DeserializeObject, belonging to the JsonConvert class.
But first, we have to query the Web site to obtain, in json/text format, the serialized information to work on.


Querying Web page

Since we do not need anything too complex, having to only retrieve the response of a Web page, we will use the WebClient class and its function DownloadString. Assuming our identification code is contained in the string variable userId, the JSON stream can be recovered with the following instructions:

 Uri tnaddress = new Uri(<TECHNET_PROFILE_URL>);
 HttpClient webClient = new HttpClient();
 HttpResponseMessage result = await webClient.GetAsync(tnaddress);
 result.EnsureSuccessStatusCode();

We prepare the URL to be queried, passing it as an argument for the function DownloadString, which will create the connection and read data in response, making them available for assignment to a variable. Now, starting from the variable result, we can proced with its deserialization, in order to 'unpack' the JSON stream.

Deserializing JSON

The function JsonConvert.DeserializeObject can be used on typed structures. Therefore we will have to define one or more classes conformed to the structure of the flow to deserialize. In image 2, we note that there is a "root" level in our stream, with the variables UserId, Points, Percentile, GoldAchievements, SilverAchievements, BronzeAchievements. The last three defines three separate classes of medals: gold, silver, bronze. Each contains different entities, i.e. Title, Description, AchievementId, Earned.

To make that kind of structure in a class format, we can sketch out something like this:

public class UserFeed{
    public String UserId {get; set;}
    public long Points { get; set; }
    public int Percentile { get; set; }
    public Medal[] GoldAchievements { get; set; }
    public Medal[] SilverAchievements { get; set; }
    public Medal[] BronzeAchievements { get; set; }
}
 
public class Medal{
    public String Title { get; set; }
    public String Description { get; set; }
    public int AchievementId { get; set; }
    public String Earned { get; set; }
}

In other words, there is a "base" class,  which we called UserFeed. Inside it, we define the properties UserId, Points, Percentile as standard types, while GoldAchievements, SilverAchievements, BronzeAchievements, referencing the same properties, can be traced to a generic class that we'll call Medal, in which we define the properties seen above, or: Title , Description, AchievementId, Earned.

Once we've finished in preparing the class that will host deserialized data, the actual process is carried out with a single line of code:


UserFeed uf = JsonConvert.DeserializeObject<UserFeed>(result.Content.ToString());

That is, given a variable uf, defined as class UserFeed, its properties will be valued using the function DeserializeObject, applied to the type UserFeed, using as input parameter in the JSON string (ie, the variable previously set by reading the Web page). From now on, any reference to the variable uf (and its properties), will expose the contents of variables as well as derived from JSON Web response.

If we want to display in a single container all the medals, whatever the type, we need to unify the three arrays of type Medal in one list. At the same time, we will have to give this list a new property, which allows to discern the type of medal (if gold, silver, bronze), information that we haven't in the three initial arrays, if not at the property name level. Although there are more elegant methods, a quick workaround may be as follows.

Prepare data structure with LINQ

Since the variable uf (as we saw above, it contains the deserialized data relating to a given TechNet profile), we could make a unique structure of medals belonging to the various types, by doing the following:

We'll create a new class that inherits the properties of the class Medal, adding a property named "ImagePath", designed to represent the physical path of an image of medal, one for each type: gold, silver, bronze. I’ve inserted those images in the Assets/img folder, so they will be included into the app package when deployed (being “img” a subfolder of Assets).

public class TypedMedal : Medal{
    public String ImagePath { get; set; }
}

Next, we'll create a List(Of TypedMedal), and using three LINQ queries we will populate it with the elements of each medal's array. For each query, we will pass the value of the "ImagePath" as due. In code, this results in:

List<TypedMedal> myMedals = new List<TypedMedal>();
                myMedals.AddRange(from Medal m in uf.GoldAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/goldmedal.png"
                                  });
 
                myMedals.AddRange(from Medal m in uf.SilverAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/silvermedal.png"
                                  });
 
                myMedals.AddRange(from Medal m in uf.BronzeAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/bronzemedal.png"
                                  });

Basically, using the method AddRange, we can add to our List(Of TypedMedal) an arbitrary number of elements of type TypedMedal. How do we get these items? Processing the three array of medals, which the uf variable contains. We'll analyze the first case only, since the other two follows the same logic.

from Medal m in uf.GoldAchievements

The first part of the query is simple: we define a local variable m, of type Medal, to be used in looping elements contained by (In) GoldAchievements array, belonging to uf. We can say that this first part of the query corresponds perfectly to a For/Each loop, and in fact we can write it as:

foreach(Medal m in uf.GoldAchievements){
    //Do something
}

The second part of the query defines what should be done at each iteration of uf.GoldAchievements. In our case, we want to create a new entity of TypedMedal type, setting inherited properties with the values shared by Medal type, but assigning the to Level property, exclusive for TypedMedal, a fixed value (which, in this case, will be "Gold Achievements"). In other words, for each Medal element analyzed, a new element of TypedMedal type will be added to the list myMedals. It will contain the same informations as Medal do, but also specifies the type of medal.

After performing the three queries, the myMedals list will be ready to be used as a view for a graphical control.


Binding to ListBox, and layout customization

Assuming to have a ListBox named MedalList on our WPF page, we want to bind it to the variable myMedals, to expose instances (and their properties) of which it is composed, providing a graphic layout through which the end user can consult it. At this point, we desire to divide the medals by type, to make clear which of them belongs to which category, i.e. gold, silver and bronze. First of all, let's see the code-behind, to further analyze the XAML aimed at achieving our final result.

MedalList.ItemsSource = myMedals;
The ItemsSource property allows to define the data collection to be used for a specific view.  Simply as that, since we have all the informations we need in the typed list myMedals, we could pass it as the source for the ListBox, managing XAML-side how the data must be managed.

In the XAML ListBox, we'll define a special section, ItemTemplate, namely the definition of the graphic elements that will produce a single row in the list.

<ListBox Name="MedalList" HorizontalAlignment="Left" Height="580" VerticalAlignment="Top" Width="400" Background="White">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Vertical" Margin="0,6,5,2" Width="auto">
                <StackPanel Orientation="Horizontal" >
                    <Image Source="{Binding ImagePath}" Stretch="None" Width="18" Height="18" Margin="0,0,6,0"/>
                    <TextBlock FontSize="20" FontWeight="Bold"   Text="{Binding Title}" Margin="0,0,0,0" FontFamily="Segoe WP"/>
                </StackPanel>
                <TextBlock FontSize="16" FontWeight="Normal" TextWrapping="Wrap" FontStyle="Normal" Text="{Binding Description}"
                              Margin="0,2,0,0" FontFamily="Segoe WP"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
 
</ListBox>

In the ItemTemplate section, a vertical StackPanel can be noticed inside it. The StackPanel contains another StackPanel. In this second one, an Image control is binded to the ImagePath property, while a first TextBox will be binded to Title. Going back to the definition of the class Medal, and considering how data were populated from JSON, we can see how these two properties define, respectively, path to the image representing a given medal, and the name of the medal obtained.
In the same fashion, the last TextBox will be binded to Description property. We can therefore expect that every element of the ListBox will be now represented by a double line of text, with the first of these lines showing the title/name of the medal and its image, and the second one showing the description.


Conclusive example


Applying the style above, and running our program, we will get a result like the following (in the image below, the query results corresponds to my profile, while i'm writing)



Class example

The complete source code of application is as follows:

Code-behind, MainPage.xaml.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Storage;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Navigation;
using Windows.Web.Http;
 
namespace TechNetMedals
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
 
            this.NavigationCacheMode = NavigationCacheMode.Required;
        }
 
        private async void Page_Loaded(object sender, RoutedEventArgs e)
        {
            int exceptionRaised = 0;
            String exceptionMsg = "";
 
            try
            {
 
                var ap = ApplicationData.Current.LocalSettings;
                String lang = "en-US";
                if (ap.Values["Language"].ToString().CompareTo("1") == 0) lang = "it-IT";
 
                Uri tnaddress = new Uri(ap.Values["TechNetURL"].ToString() + lang);
                HttpClient webClient = new HttpClient();
                HttpResponseMessage result = await webClient.GetAsync(tnaddress);
                result.EnsureSuccessStatusCode();
 
                UserFeed uf = JsonConvert.DeserializeObject<UserFeed>(result.Content.ToString());
 
                List<TypedMedal> myMedals = new List<TypedMedal>();
 
                myMedals.AddRange(from Medal m in uf.GoldAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/goldmedal.png"
                                  });
 
                myMedals.AddRange(from Medal m in uf.SilverAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/silvermedal.png"
                                  });
 
                myMedals.AddRange(from Medal m in uf.BronzeAchievements
                                  select new TypedMedal
                                  {
                                      Title = m.Title,
                                      Description = m.Description,
                                      AchievementId = m.AchievementId,
                                      Earned = m.Earned,
                                      ImagePath = "/Assets/img/bronzemedal.png"
                                  });
 
 
                MedalList.ItemsSource = myMedals;
            }
            catch (Exception ex)
            {
                exceptionRaised = 1;
                exceptionMsg = ex.Message;
            }
 
            if (exceptionRaised == 1)
            {
                var dialog = new MessageDialog("Error in retrieving data. Please check you connectivity, and your UserID on Settings page.");
                var result = await dialog.ShowAsync();
            }
        }
 
        private void AppBarButton_Click(object sender, RoutedEventArgs e)
        {
            Frame.Navigate(typeof(SettingsPage));
        }
    }
}

XAML, MainPage.xaml

<Page
    x:Class="TechNetMedals.MainPage"
    xmlns:local="using:TechNetMedals"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    Loaded="Page_Loaded">
    <Page.BottomAppBar>
        <CommandBar>
            <AppBarButton Icon="Edit" Label="Settings" Click="AppBarButton_Click"/>
        </CommandBar>
    </Page.BottomAppBar>
 
    <Grid>
        <ListBox Name="MedalList" HorizontalAlignment="Left" Height="580" VerticalAlignment="Top" Width="400" Background="White">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="0,6,5,2" Width="auto">
                        <StackPanel Orientation="Horizontal" >
                            <Image Source="{Binding ImagePath}" Stretch="None" Width="18" Height="18" Margin="0,0,6,0"/>
                            <TextBlock FontSize="20" FontWeight="Bold"   Text="{Binding Title}" Margin="0,0,0,0" FontFamily="Segoe WP"/>
                        </StackPanel>
                        <TextBlock FontSize="16" FontWeight="Normal" TextWrapping="Wrap" FontStyle="Normal" Text="{Binding Description}"
                                      Margin="0,2,0,0" FontFamily="Segoe WP"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
 
        </ListBox>
 
    </Grid>
</Page>

Settings page, or avoiding manual HTML search 

In the MainPage.xaml.cs code above, we can see the URI that will be queried to retrieve profile informations is formed as follows:

 var ap = ApplicationData.Current.LocalSettings;
 String lang = "en-US";
 if (ap.Values["Language"].ToString().CompareTo("1") == 0) lang = "it-IT";
 Uri tnaddress = new Uri(ap.Values["TechNetURL"].ToString() + lang);

In other words, somewhere in our app we will have a setting page, which allows us to specify two LocalSettings, namely “Language” and “TechNetURL”. To avoid giving the final user the task of finding its own profile ID, we will make a simple HTML parsing, letting the app to find that data.
The settings page will look like that:



In the first field, the final user must enter the alias used in TechNet to view its profile (which corresponds to your name plus surname).



The app then proceeds in reaching https://social.msdn.microsoft.com/profile/<YOUR_ENTERED_DATA/, downloading the page HTML and parsing it to find profile ID from the script loadReputation. It will be done by doing:

Uri tnaddress = new Uri("https://social.technet.microsoft.com/profile/" + txtProfileURL.Text);
HttpClient webClient = new HttpClient();
HttpResponseMessage result = await webClient.GetAsync(tnaddress);
result.EnsureSuccessStatusCode();
 
htmlSource = result.Content.ToString();
htmlSource = htmlSource.Substring(htmlSource.IndexOf("loadReputation(") + 16);
htmlSource = htmlSource.Substring(0, htmlSource.IndexOf("/detail"));
htmlSource += "/detail?locale=";
 
ap.Values["TechNetURL"] = htmlSource;

The data referring language is simply obtained by the index of a ComboBox: in my case, i’ve made the value zero corresponding to en-US, and 1 to it-IT.

Code-behind, SettingsPage.xaml.cs

using TechNetMedals.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Graphics.Display;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Storage;
using Windows.Web.Http;
 
namespace TechNetMedals
{
    public sealed partial class SettingsPage : Page
    {
        private NavigationHelper navigationHelper;
        private ObservableDictionary defaultViewModel = new ObservableDictionary();
 
        public SettingsPage()
        {
            this.InitializeComponent();
 
            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
            this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
        }
 
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }
 
        public ObservableDictionary DefaultViewModel
        {
            get { return this.defaultViewModel; }
        }
 
 
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedTo(e);
        }
 
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedFrom(e);
        }
 
        private void Close_Click(object sender, RoutedEventArgs e)
        {
            this.Frame.Navigate(typeof(MainPage));
        }
 
        private async void Save_Click(object sender, RoutedEventArgs e)
        {
            var ap = ApplicationData.Current.LocalSettings;
            ap.Values["ProfileURL"] = txtProfileURL.Text;
            ap.Values["Language"] = cmbLang.SelectedIndex.ToString();
 
            String htmlSource = "";
            try
            {
                Uri tnaddress = new Uri("https://social.technet.microsoft.com/profile/" + txtProfileURL.Text);
                HttpClient webClient = new HttpClient();
                HttpResponseMessage result = await webClient.GetAsync(tnaddress);
                result.EnsureSuccessStatusCode();
 
                htmlSource = result.Content.ToString();
                htmlSource = htmlSource.Substring(htmlSource.IndexOf("loadReputation(") + 16);
                htmlSource = htmlSource.Substring(0, htmlSource.IndexOf("/detail"));
                htmlSource += "/detail?locale=";
 
                ap.Values["TechNetURL"] = htmlSource;
            }
            catch {
                ap.Values["TechNetURL"] = "";
            }         
             
            Close_Click(sender, e);
        }
 
        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            var ap = ApplicationData.Current.LocalSettings;
            try
            {
                txtProfileURL.Text = ap.Values["ProfileURL"].ToString();
                cmbLang.SelectedIndex = int.Parse (ap.Values["Language"].ToString() );
            } catch {
            }
        }
    }
}

Demo


The sample project can be downloaded at: https://code.msdn.microsoft.com/Making-a-Windows-Phone-App-a143c105

Sample video


A sample video could be seen here:  http://www.youtube.com/watch?v=EDl3u36F2Ys