In the first part,
I covered how to get the initial project setup and share the ViewModels and ViewModelLocator between a Windows Phone 8 project and a Windows Store application.
In this post, I'll add a DataService and a Model class that will be used to get the most recent posts from the Windows Phone
blog and display the results.
Adding DataService and Async to Portable Class Library
The purpose of the DataService
in the context of this project is to retrieve the RSS feed from the Windows Phone
blog (http://blogs.windows.com/windows_phone/b/wpdev/rss.aspx)
using the async methods available in the 4.5 Framework, and return a collection of our
Headline
class object for the UI to handle and display.
Headline Class Model
The Headline
class is the object that will be loaded and a collection of these will be built and returned from the data service method.
Create a new folder in the Mvvm.PCL project called "Model" and add a new file called
Headline.cs.
public class Headline
{
public string Title { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public DateTime Published { get; set; }
}
IDataService
In good practice, create an interface for the DataService
class. This would allow for taking advantage of dependency injection if you chose to do so.
Add a new interface file to the model folder called IDataService.cs. Here is the interface:
namespace Mvvm.PCL.Model
{
public interface IDataService
{
void GetHeadlines(Action<List<Headline>, Exception> callback);
}
}
The interface defines a single method that accepts a delegate with a collection or List<T>
of Headline
s and an Exception
parameter.
DataService
Next, in the same folder, add the DataService.cs file and implement the interface.
public class DataService : IDataService
{
public void GetHeadlines(Action<List<Headline>, Exception> callback)
{
throw new NotImplementedException();
}
}
HttpWebRequest
Most simple requests for data are done with the WebClient
class,
however this class is not available in Portable Class libraries and is really only an abstraction of what must be used and that is the
HttpWebRequest
.
Add a new method to the class called MakeAsyncRequest
accepting a
URL (string) as a parameter and set the method to return a Task<string>
. Within the method,
I'll use the Task.Factory.FromAsync
method to call the URL asynchronously returning the
Task<WebRequest>
then use a continuation to read the WebResponse
.
private static Task<string> MakeAsyncRequest(string url)
{
HttpWebRequest request = WebRequest.CreateHttp(url);
request.Method = "GET";
Task<WebResponse> task = Task.Factory.FromAsync(
request.BeginGetResponse,
(asyncResult) => request.EndGetResponse(asyncResult),
(object)null);
return task.ContinueWith(t => ReadStreamFromResponse(t.Result));
}
private static string ReadStreamFromResponse(WebResponse response)
{
using (Stream responseStream = response.GetResponseStream())
using (StreamReader sr = new StreamReader(responseStream))
{
string strContent = sr.ReadToEnd();
return strContent;
}
}
The GetHeadlines
method can now be completed. First add the static URL.
private readonly string uri = "http://blogs.windows.com/windows_phone/b/wpdev/rss.aspx";
Then declare a variable to hold the results of the MakeAsyncRequest
method and set the call with the
await
keyword so the UI thread is not blocked.
var t = await MakeAsyncRequest(uri);
You will also have to mark the method as async
or the compiler will give you an error telling you to do so.
public async void GetHeadlines(Action<List<Headline>, Exception> callback)
{
...
}
The results returned are a string type and there are a couple of options to get it into a nice format to work with. Your first option might be to use
the Silverlight SyndicationFeed
class
which is in the System.ServiceModel.Syndication
namespace. However, it is not inherently available in the portable classes and you'll need to go searching for it on your dev
machine. Hint (C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\System.ServiceModel.Syndication.dll).
I'm choosing to use LINQ and doing LINQ to XML here to get what I need out of the results string and inflate my classes and return it.
Here is the completed method:
public async void GetHeadlines(Action<List<Headline>, Exception> callback)
{
Exception err = null;
List<Headline> results = null;
try
{
var t = await MakeAsyncRequest(uri);
StringReader stringReader = new StringReader(t);
using (var xmlReader = System.Xml.XmlReader.Create(stringReader))
{
var doc = System.Xml.Linq.XDocument.Load(xmlReader);
results = (from e in doc.Element("rss").Element("channel").Elements("item")
select
new Headline()
{
Title = e.Element("title").Value,
Description = e.Element("description").Value,
Published = Convert.ToDateTime(e.Element("pubDate").Value),
Url = e.Element("link").Value
}).ToList();
}
}
catch (Exception ex)
{
err = ex;
}
callback(results, err);
}
That covers all of the code needed in the Portable Class(es) for getting the data, just need to edit the
MainViewModel
class constructor to create the DataService
class,
implement the new method, and create a Headlines
property.
MainViewModel
Add a new property for the headlines to be bound to by the UI.
private List<Model.Headline> _headlines;
public List<Model.Headline> Headlines
{
get { return _headlines; }
set
{
_headlines = value;
RaisePropertyChanged(() => Headlines);
}
}
In the constructor, create an instance of the DataService
and execute the method. I did mention earlier that there is an
IDataService
for DI, but for this example a concrete DataService
class is created.
public MainViewModel()
{
var service = new DataService();
service.GetHeadlines((headlines, err) => {
if (err != null)
{
System.Diagnostics.Debug.WriteLine(err.ToString());
}
else
{
this.Headlines = headlines;
}
});
}
Adding the UI Elements
Windows Phone
Open the MainPage.xaml page and wrap the previous TextBlock
from part 1 in a
StackPanel
then add a ListBox
. Set the ItemsSource
property of the
ListBox
to {Binding Headlines, Mode=TwoWay}
, then add a simple template with a
TextBlock
to show the title of the story.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBlock Text="{Binding Hello, Mode=TwoWay}" Foreground="White" FontSize="18" />
<ListBox ItemsSource="{Binding Headlines, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<TextBlock Text="{Binding Title}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Windows Store
The store app is just as easy. In MainPage.xaml, add a GridView
control, set the
ItemsSource
to {Binding Headlines, Mode=TwoWay}
, but in this case I'll define
an ItemTemplate
outside of the control called PCLItemTemplate
and display the Title and the Description.
<GridView ItemsSource="{Binding Headlines, Mode=TwoWay}" Grid.Row="1" ItemTemplate="{StaticResource PCLItemTemplate}" />
<Page.Resources>
<DataTemplate x:Key="PCLItemTemplate">
<StackPanel Orientation="Vertical" Width="500" Height="250">
<TextBlock Foreground="White" Text="{Binding Title}" FontSize="18"
HorizontalAlignment="Center" Margin="20,10,20,0" TextTrimming="WordEllipsis"/>
<TextBlock Foreground="White" Text="{Binding Description}"
Style="{StaticResource ItemTextStyle}" HorizontalAlignment="Center" Margin="20,10" TextTrimming="WordEllipsis"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
Summary
Running either apps presents the data in a different context and there is complete control as to the presentation and design choices based on the platform.
If you are looking to create Windows Store apps and/or Windows Phone applications Portable Class Libraries is a great way to leverage code within your multiple
platform target solution. MvvmLight is a choice of mine and many others and fits very well too thanks to others in the community.