Comment Feed for Channel 9 - Handling Asynchronous Data in Windows Store Appshttp://video.ch9.ms/ch9/5c1e/f4c8edf4-6a7d-4b94-aad3-17157dac5c1e/Phone7toWin8QS02_220.jpgChannel 9 - Handling Asynchronous Data in Windows Store Apps This video covers the basics of working with data in the Windows 8 Khan Academy app. We go over the new asynchronous pattern in WinRT for handling requests to and from the server, as well as putting together the Data Model for our app. If you have never worked with async patterns before, the following async overview may be helpful before proceeding with our Khan Academy samples. An Overview of Async / AwaitAs referenced above, asynchronous APIs are found all over the Windows 8 runtime and they are a key concept in Windows 8 app development. We have already seen the async / await pattern in action in the first Quickstart of this series when discussing the LaunchApp() method on startup: App.xaml.cs
public async void LaunchApp(
ApplicationExecutionState previousExecutionState)
{
DataSource = new KhanDataSource();
await DataSource.LoadAllData();
Notice the use of the async keyword in the method’s declaration. Since this method is going to use an await operator for an asynchronous task, we need to use the async keyword in the declaration. In our case, we use await after we create our Data Model named DataSource and request it to populate itself by calling its LoadAllData() function. LoadAllData() handles both local and remote requests for Khan Academy video playlist data. The Windows 8 Khan Academy video app ships with a cached snapshot of the entire playlist library stored locally. This keeps the UI fast and fluid since the app doesn’t need to request and parse the entire collection of videos from the internet before launching for the first time. Each time the app runs, though, the KhanDataSource.LoadAllData() function in our Data Model also makes a call to the Khan Academy server and updates the local cache with any updates behinds the scenes, before reloading the local data to update any bindings. LoadRemoteData() is also implements the async/await pattern and is shown below: C#
private async void LoadRemoteData()
{
HttpClient client = new HttpClient();
client.MaxResponseContentBufferSize = Megabyte * 20;
HttpResponseMessage response = await client.GetAsync(
new Uri(“http://www.khanacademy.org/api/v1/topictree”));
await WriteLocalCacheAsync(
await response.Content.ReadAsStringAsync(),
@”cache\topictree”);
await LoadCachedData();
}
LoadRemoteData() first creates and qualifies an HttpClient object but then needs to await the asynchronous HttpResponseMessage from the server before continuing. So far, this is a straightforward async/await pattern. But some tasks may require a nesting of await commands to complete the desired transaction. We can see this in the code once we want to write the response locally. In order to write to the local cache, which is itself an async operation, we first need to read the response as a string, which also requires an await command: C#
await WriteLocalCacheAsync(
await response.Content.ReadAsStringAsync(),
@”cache\topictree”);
Once the local cache has been updated with the most recent data retrieved from the Khan Academy web servers, we refresh the local Data Source from this updated cache. In the Khan Academy Windows 8 app, the majority of code that handles the asynchronous data is contained in the KhanDataSource.cs file and follows the similar structure as shown above. Handling the JSON ResponseAs we saw in the previous section, the Khan Academy app keeps the local cache of app data up to date by overwriting the local cache with updated JSON data from the Khan Academy servers. With Windows 8, there are multiple ways of processing JSON data, based on your needs and situation. For our scenario, we knew the shape of the data being returned by Khan Academy, so we first created a custom class that would represent each logical node of the response: C#
[DataContract]
public class JsonNode
{
[DataMember(Name = “children”)]
public JsonNode[] Children { get; set; }
[DataMember(Name = “kind”)]
public string Kind { get; set; }
[DataMember(Name = “ka_url”)]
public string Url { get; set; }
[DataMember(Name = “title”)]
public JsonNode[] Title{ get; set; }
[DataMember(Name = “description”)]
public string Description { get; set; }
[…]
}
When it is time to read this locally cached JSON response, we invoke the following method and pass in our custom class type: KhanDataSource.cs
private async Task LoadCachedData()
{
JsonNode cached = await ReadLocalCacheAsync&lt;JsonNode&gt;(
@&quot;cache\topictree.json&quot;,
@&quot;data\topictree.json&quot;);
PopulateGroups(cached);
}
private static async Task&lt;T&gt; ReadLocalCacheAsync&lt;T&gt;(
string filename,
StorageFolder folder)
where T : class
{
try
{
var file = await folder.GetFileAsync(filename);
string result = await FileIO.ReadTextAsync(file);
var serializer = new DataContractJsonSerializer(typeof(T));
var memStream = new MemoryStream(
Encoding.UTF8.GetBytes(result));
var serializedResult = serializer.ReadObject(memStream) as T;
return serializedResult;
}
catch (FileNotFoundException)
{
return default(T);
}
}
As you can see, ReadLocalCacheAsync creates a new DataContractJsonSerializer of the type specified. The string result of reading the local cache file is wrapped in a MemoryStream and passed to the JSON serializer. Once serialized, we have an object representation of the cached text data. The cached response is then sent to the PopulateGroups() function to update our Data Model. If we look at the first part of the PopulateGroups() function, you'll see how we are using LINQ to pull out a properly formatted collection of elements for use in our Data Model. C#
private void PopulateGroups(JsonNode root)
{
if (root == null) return;
Func&lt;JsonNode, bool&gt; videoClause = v =&gt; v.Kind == &quot;Video&quot;;
Func&lt;JsonNode, bool&gt; topicClause = c =&gt; c.Kind == &quot;Topic&quot;;
var playlists = root.Children
.SelectMany(group =&gt; group.Children.Where(topicClause)
.Flatten(g =&gt; g.Children.Where(topicClause))
.Select(k =&gt; new PlaylistItem
{
Name = k.Title,
Description = k.Description,
Slug = group.Title,
SourceNode = k
})
.OrderBy(k =&gt; k.Slug));
[…]
}
Populating the Data ModelThe Khan Academy API returns a series of playlists, made up of video items. However, the content of these playlists spans many different academic topics, so we want to make sure we further organize the playlists according to these topics. Here is a part of the Class Diagram to illustrate the relationship. If we look at the last part of the PopulateGroups() method , the videos are subsequently organized under their individual playlists. This ungrouped collection of populated playlists is then sent to CreateGroups() which organizes them into their respective Topics. The relevant code looks like this: C#
private void PopulateGroups(JsonNode root)
{
[…]
foreach (var playlist in playlists)
{
// now load all the videos
var videos = playlist.SourceNode
.Children
.Flatten(v =&gt; v.Children);
foreach (var video in videos.Where(videoClause).Select(
v =&gt; new VideoItem
{
Name = v.Title,
Description = v.Description,
ImagePath = v.Downloads != null ?
new Uri(v.Downloads.Screenshot) : null,
VideoPath = v.Downloads != null ?
new Uri(v.Downloads.Video) : null,
KhanPath = new Uri(v.Url),
Parent = playlist.Name,
DateAdded = DateTime.Parse(v.DateAdded)
}))
{
playlist.Videos.Add(video);
}
}
if (playlists.Count() &gt; 0)
{
SortGroups(
TopicItem.CreateGroups(
playlists.Where(p =&gt; p.Videos.Count &gt; 0)));
}
}
public static IEnumerable&lt;TopicItem&gt;
CreateGroups(IEnumerable&lt;PlaylistItem&gt; ungroupedPlaylists)
{
var grouped = ungroupedPlaylists
.GroupBy(i =&gt; i.GroupKey)
.Select(g =&gt; new TopicItem(g.Key, g.Key &#43; &quot; description&quot;)
{
ListSetter = g
});
var res = grouped
.OrderBy(i =&gt; i.Order)
.ThenByDescending(i =&gt; i.Playlists.Count);
return res;
}
In this way, we are able to get unsorted JSON data from Khan Academy and organize it into a series of classes that represent the actual videos, their topical playlist and their high level, academic grouping of topics. All this information is stored in ObservableCollections which we will bind to the UI in the next Quickstart. If you have any questions, comments, or feedback feel free to join in the discussion. Twitter: @rickbarraza, @joelmartinez enThu, 22 Feb 2018 07:23:34 GMTThu, 22 Feb 2018 07:23:34 GMTRev9