Using Social Networks to Build Reusable Architecture for the Windows Phone 7 – Part IV

24. February 2011


Part I
Part II
Part III
Part IV

This is the fourth installment of a several part series/journey to use social network resources for Windows Phone 7 development and produce a reusable code base.

 

Using the Web to Find Data

There are great how-to articles about consuming data in your Windows Phone applications.  You can find some of them using the Reddit.com site.  For example, navigate to Reddit.com and in the search field enter a search like, “wp7dev web services”.  Among the results might be a link to the MSDN article, “Connecting to Web and Data Services for Windows Phone”.

On Create.MSDN.com there is a quick start tutorial that uses the Bing Search API.  It can be found here.  You can search through the site for other examples and tutorials.  Like many web service APIs, the Bing Search API requires a key to use it.  This tutorial will help you understand  how to consume a web service that doesn’t require a key.

After doing a some web research you might find a web service like this one located at http://www.iheartquotes.com/.  The service returns random quotes.  The site explains how to use the web service on its API page.  Developers can request one of several formats such as text, JSON, or HTML.  There are also a bunch of sources to get quotes from like starwars and hitchikersguide.  Here is the link we will use in this application: http://www.iheartquotes.com/api/v1/random?source=oneliners.   If you navigate to the link directly, you will get back text data by default.  Because we want to get JSON formatted data we add the parameter format=json to the end of the Url.  Each time the service returns it returns a new random quote with this same structure.  So, this is perfect.

{"json_class":"Fortune","tags":["oneliners"],"quote":"Never trust a stockbroker who's married to a travel agent.","link":"http://iheartquotes.com/fortune/show/7326","source":"oneliners"}

Consuming the Data

Now to understand how to consume this data in a Windows Phone 7 application we need to search for the text, “How to parse JSON on Windows Phone 7”.  It turns out that is a great question to search for on the web because it leads to an article on MSDN here: DataContractJsonSerializer Class.  We need to deserialize the JSON string to a C# object with the same structure.  So in the WindowsPhoneApplication1.Shared project, we add a new folder called Models and the a new class called Quote.cs.  Then we make a plain old CLR object (POCO) based on the properties of the JSON.  There are two modifications we need to make to the class.  First, implement the INotifyPropertyChanged interface so that updates to values of these properties will update any Data Bindings used in the UI.  Second, we have to prepare the class for deseriliazation by adding the DataContractAttribute to the class and the DataMemberAttribute to each property that will be deserialized.  The moment we attempt to add the [DataContract] attribute to the class we need a reference to System.Runtime.Serialization.  So in the Shared project simply add the reference and finish decorating the properties in the class.  Notice too that the Name parameter of the DataMember attribute needs to be set to the proper casing of the properties returned in the JSON data.  This creates the correct mapping to properties during deserialization.  The final class is shown below.

using System.ComponentModel; using System.Runtime.Serialization; namespace WindowsPhoneApplication1.Shared.Models { [DataContract] public class Quote : INotifyPropertyChanged { private string randomQuote; [DataMember(Name="quote")] public string RandomQuote { get { return randomQuote; } set { if (randomQuote == value) return; randomQuote = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("RandomQuote")); } } private string source; [DataMember(Name="source")] public string Source { get { return source; } set { if (source == value) return; source = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Source")); } } public event PropertyChangedEventHandler PropertyChanged; } }

 

The DataContractJSonSerializer class is going to be used to serialize and deserialize the JSON string to an object defined by this class.  Going back to the MSDN article we see the class is located in another assembly System.ServiceModel.Web.  The location of the code that does this work is going to be in the WindowsPhone.Helpers project.  So we add the reference to that project and create a helper class with static methods to do some JSON serialization.  The helper class source is listed below.

using System.IO; using System.Runtime.Serialization.Json; using System.Text; namespace WindowsPhone.Helpers { public class JsonSerialization { public static string Serialize(object obj) { // Serialize to a memory stream.... MemoryStream ms = new MemoryStream(); // Serialize to memory stream with DataContractJsonSerializer DataContractJsonSerializer s = new DataContractJsonSerializer(obj.GetType()); s.WriteObject(ms, obj); byte[] json = ms.ToArray(); ms.Close(); // return utf8 encoded json string return Encoding.UTF8.GetString(json, 0, json.Length); } public static T Deserialize<T>(string json) { T result = default(T); // load json into memorystream and deserialize MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)); DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(T)); result = (T)s.ReadObject(ms); ms.Close(); return result; } } }

 

Getting Design Time Data from the Web Service

It can be fairly easy to create a web request given the information up to this point, deserialize the result, and show it in the UI of the application.  In a nutshell that is the goal. But we want to be able to have design time data and build a slightly more robust structure.  So we are going to build a custom client and then wrap that client.  The custom client has an asynchronous call to the Url for the Quote API that when complete will deserialize the result and return the Quote object in an event.  The wrapper class for the client will have a public method to start the request and will listen to the completed event to return the Quote object.  This approach allows us to create an interface on the client wrapper for use in IoC and thereby create a design time data implementation of the wrapper.  The interface is located in the Shared project in a folder called WebServices.  We add a copy of the wrapper class to the Design project that inherits the interface.  The difference in the Design copy is that instead of calling into the actual client for the web service, we can simply instantiate a Quote object, populate it, and return it to the design surface.  To take a step back, the client and wrapper classes will be located in the main phone project inside a folder called WebServices.  The Design project copy of the wrapper will be located in a folder also called WebServices.

There is an additional helper for making passing event arguments a little easier.  That article can be found here: Custom Generic EventArgs.  Simply add that to the source code in the WindowsPhone.Helpers project and correct namespaces as needed.

 

References, References, References

To get all this to tie together and compile there are a few required references.  The Shared project has to reference the Helpers project.  The Design project has to reference the Helpers and Shared projects.  The main phone application project has to reference Helpers and Shared projects just like the Design project does.  The good thing is that so far there is no circular reference and we get design time data for a web service!  Here is what it looks like now in the solution tree.

image

 

Hooking Up Design Time Data

Now it is time to add the design time and run time objects for the web service to the ViewModelLocator class.  Again, if the code is in design time the class from the Design project has to be registered to the IoC container.  If not, the production web service wrapper class is registered.  The MainViewModel class will have a property for the web service wrapper so that means a few additional changes are needed.  The interface needs the property.  MainViewModel needs the implementation of that property.  At the time the MainViewModel is resolved in ViewModelLocator, the web service wrapper is resolved too and assigned to the new property.  Finally, the design time wrapper class for the web service returns data.  Not from the web service client, but from the wrapper alone.

ViewModelLocator.cs
/* In App.xaml: <Application.Resources> <vm:ViewModelLocator xmlns:vm="clr-namespace:WindowsPhoneApplication1.ViewModels" x:Key="Locator" /> </Application.Resources> In the View: DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}" */ using System.IO; using System.Reflection; using GalaSoft.MvvmLight; using MicroIoc; using WindowsPhoneApplication1.Shared.ViewModels; using WindowsPhoneApplication1.WebServices; using WindowsPhoneApplication1.Shared.WebServices; namespace WindowsPhoneApplication1.ViewModels { /// <summary> /// This class contains static references to all the view models in the /// application and provides an entry point for the bindings. /// </summary> public class ViewModelLocator { private readonly IMicroIocContainer container = new MicroIocContainer(); private Assembly designTypes = null; /// <summary> /// Initializes a new instance of the ViewModelLocator class. /// </summary> public ViewModelLocator() { if (ViewModelBase.IsInDesignModeStatic) { try { //Load the non-referenced assembly containing design time ViewModels and Services designTypes = Assembly.Load("WindowsPhoneApplication1.Design, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); } catch (FileNotFoundException) { designTypes = Assembly.LoadFrom(@"C:\code\WP7\WindowsPhoneApplication1\WindowsPhoneApplication1.Design\BuildFiles\WindowsPhoneApplication1.Design.dll"); } // Register design time view models and services container.RegisterInstance(designTypes.GetType("WindowsPhoneApplication1.Design.ViewModels.MainViewModel"), "MainViewModel_DesignTime"); container.RegisterInstance(designTypes.GetType("WindowsPhoneApplication1.Design.WebServices.QuoteServiceWrapper"), "QuoteService_DesignTime"); } else { // Register run time view models container.RegisterInstance(typeof(MainViewModel), "MainViewModel"); container.RegisterInstance(typeof(QuoteServiceWrapper), "QuoteService"); } } public IMainViewModel Main { get { if (ViewModelBase.IsInDesignModeStatic) { IMainViewModel mainViewModel = (IMainViewModel)container.Resolve(designTypes.GetType("WindowsPhoneApplication1.Design.ViewModels.MainViewModel"), "MainViewModel_DesignTime"); mainViewModel.QuoteService = (IQuoteService)container.Resolve(designTypes.GetType("WindowsPhoneApplication1.Design.WebServices.QuoteServiceWrapper"), "QuoteService_DesignTime"); return mainViewModel; } else { IMainViewModel mainViewModel = GetViewModel<MainViewModel>(); mainViewModel.QuoteService = (IQuoteService)container.Resolve(typeof(QuoteServiceWrapper), "QuoteService"); return mainViewModel; } } } private T GetViewModel<T>() where T : ViewModelBase { // Create a new view model T vm = container.Resolve<T>(); return vm; } } }
IMainViewModel.cs
using WindowsPhoneApplication1.Shared.Models; using WindowsPhoneApplication1.Shared.WebServices; namespace WindowsPhoneApplication1.Shared.ViewModels { public interface IMainViewModel { string ApplicationTitle { get; set; } IQuoteService QuoteService { get; set; } Quote Quote { get; set; } } }
MainViewModel.cs
using GalaSoft.MvvmLight; using System; using WindowsPhoneApplication1.Shared.ViewModels; using WindowsPhoneApplication1.Shared.WebServices; using WindowsPhoneApplication1.Shared.Models; using System.ComponentModel; namespace WindowsPhoneApplication1.ViewModels { /// <summary> /// This class contains properties that a View can data bind to. /// <para> /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel. /// </para> /// <para> /// You can also use Blend to data bind with the tool's support. /// </para> /// <para> /// See http://www.galasoft.ch/mvvm/getstarted /// </para> /// </summary> public class MainViewModel : ViewModelBase, IMainViewModel { /// <summary> /// Initializes a new instance of the MainViewModel class. /// </summary> public MainViewModel() { } private IQuoteService quoteService; public IQuoteService QuoteService { get { return quoteService; } set { if (quoteService == value) return; quoteService = value; GetRandomQuote(); } } private void GetRandomQuote() { this.QuoteService.GetQuoteCompleted += (s, e) => { this.Quote = e.Value; }; this.QuoteService.GetQuote(); } private Quote quote; public Quote Quote { get { return quote; } set { if (quote == value) return; quote = value; RaisePropertyChanged("Quote"); } } /// <summary> /// The <see cref="ApplicationTitle" /> property's name. /// </summary> public const string ApplicationTitlePropertyName = "ApplicationTitle"; private string applicationTitle = "This is an awesome phone application template!"; /// <summary> /// Gets the ApplicationTitle property. /// TODO Update documentation: /// Changes to that property's value raise the PropertyChanged event. /// This property's value is broadcasted by the Messenger's default instance when it changes. /// </summary> public string ApplicationTitle { get { return applicationTitle; } set { if (applicationTitle == value) { return; } var oldValue = applicationTitle; applicationTitle = value; // Update bindings, no broadcast RaisePropertyChanged(ApplicationTitlePropertyName); } } ////public override void Cleanup() ////{ //// // Clean own resources if needed //// base.Cleanup(); ////} } }

WindowsPhoneApplication1.Design.WebServices.QuoteServiceWrapper.cs
using System; using WindowsPhone.Helpers; using WindowsPhoneApplication1.Shared.Models; using WindowsPhoneApplication1.Shared.WebServices; namespace WindowsPhoneApplication1.Design.WebServices { public class QuoteServiceWrapper : IQuoteService { public void GetQuote() { Quote quote = new Quote() { Source = "Lorem Ipsum (translation)", RandomQuote = "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain.... " }; if (GetQuoteCompleted != null) GetQuoteCompleted(this, new EventArgs<Quote>(quote)); } public event EventHandler<EventArgs<Quote>> GetQuoteCompleted; } }

Showing the Design Time Data

The MainView.xaml can now display design time data!  Because of all the infrastructure in place, all that needs to be done is to add standard Silverlight data binding to the View.  The View is unaware of everything else going on in the VIewModel, Locator, and other architecture.  All it knows is that there is a value for a Quote, and it is bindable.  It seems like a lot of extra code to make this separation of concerns between the View and ViewModel happen.  The alternative is not to use MVVM and deal with the increase in cost later when you need to change the code.  This is a much more maintainable setup.  The XAML below shows the bindings for a quote and source.

MainView.xaml
<phone:PhoneApplicationPage x:Class="WindowsPhoneApplication1.Views.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" shell:SystemTray.IsVisible="True" DataContext="{Binding Main, Source={StaticResource Locator}}"> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="24,24,0,12"> <TextBlock x:Name="ApplicationTitleTextBlock" Text="{Binding ApplicationTitle}" Style="{StaticResource PhoneTextNormalStyle}" /> <ScrollViewer> <StackPanel> <TextBlock x:Name="QuotationTextBlock" Text="{Binding Quote.RandomQuote}" TextWrapping="Wrap" Margin="-3,-8,0,0" Style="{StaticResource PhoneTextTitle1Style}" /> <TextBlock x:Name="SourceTextBlock" Text="{Binding Quote.Source}" Margin="-3,-8,0,0" Style="{StaticResource PhoneTextTitle2Style}" /> </StackPanel> </ScrollViewer> </StackPanel> </Grid> <!-- Sample code showing usage of ApplicationBar <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton x:Name="appbar_button1" IconUri="/Images/appbar_button1.png" Text="Button 1"></shell:ApplicationBarIconButton> <shell:ApplicationBarIconButton x:Name="appbar_button2" IconUri="/Images/appbar_button2.png" Text="Button 2"></shell:ApplicationBarIconButton> <shell:ApplicationBar.MenuItems> <shell:ApplicationBarMenuItem x:Name="menuItem1" Text="MenuItem 1"></shell:ApplicationBarMenuItem> <shell:ApplicationBarMenuItem x:Name="menuItem2" Text="MenuItem 2"></shell:ApplicationBarMenuItem> </shell:ApplicationBar.MenuItems> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> --> </phone:PhoneApplicationPage>

Here is the result of the design time data in Visual Studio.  We can see the Application Title, and a quote on screen.  The source of the quote is implemented but not visible in this layout.

image

Summary

This post outlined the steps to add a web service to the solution and show design time data from it.  The application is taking shape.  Next time we will add a custom Application Bar to be able to refresh the quote.  Then we’ll add some nice finishing touches that take advantage of some other features in the solution.  It gets much easier from this point forward!

 

Source Code

Code, Silverlight, WP7

Comments are closed