Treasure Map under the bonnet (hood) #6 … News Island
Now that we have covered the Windows Store App specific design and coding adventures, we can make a quick detour through some other coding gems. Today we will peek behind the solution scene to explore the News feature.
News Feed Feature
Those familiar with the first version of the ALM Rangers Treasure Map with recognise two new island categories, namely Favourites and News. The Treasure Map under the bonnet (hood) #5 … Finishing touches, but not yet finished post briefly mentioned the ability to mark categories and projects as favourites using the AppBar … therefore we can skip that island and sail to the News island.
When we select (click) the News island we are presented with an aggregated list of news titles and summary extracts. In this post we will investigate where these gems come from.
If we look into the TreasureMapDataModel.xml configuration file we recognise a news tag, embracing five (5) RSS feeds. You can explore these feeds to validate that the entries above are indeed legit and to dig into more of the details of each News post.
To explore the News feature you need to visit two areas of the Windows Store App solution.
As always we recommend the use of the CodeMap feature in Visual Studio to visually map the dependencies and execution of the code.
It is evident that the RangersNewsFeed is referenced and called by both the App when initializing and when navigating to the the News View.
Again the team uses Async features to ensure a seemingly instantaneous application initialisation and not bottleneck the application performance and behaviour through the loading of the static configuration for the map and the expensive retrieval of News items.
The code contains some regular expressions. Use the UNISA Chatter – Design patterns in C++ Part 6: Widgets Validation and Regular Expressions post created a long, long ago if you need a quick reference sheet for regular expressions.
Code Samples
These are strictly sample code extracts and may most likely have been updated in the interim to meet quality bars, support new features or other code churn factors.
App Class Constructor
1: public App()
2: {
3: this.InitializeComponent();
4: RangersNewsFeed = new RangersNewsFeed();
5: Database = new DB();
6: Database.LoadDBAsync().ContinueWith(t =>
7: {
8: TileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();
9: TileUpdater.EnableNotificationQueue(true);
10: TileGenerator.GeneratePercentageTile();
11:
12: RangersNewsFeed.LoadItemsAsync().ContinueWith(_ =>
13: {
14: UpdateNewsTiles();
15: });
16: }).Wait();
17: }
RangerNewsFeed.cs
1: //-----------------------------------------------------------------------
2: // <copyright file="RangersNewsFeed.cs" company="Microsoft Corporation">
3: // Copyright Microsoft Corporation. All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.) This is sample code only, do not use in production environments.
4: // </copyright>
5: //-----------------------------------------------------------------------
6:
7: namespace Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp.News
8: {
9: using System;
10: using System.Collections.Generic;
11: using System.Linq;
12: using System.Text.RegularExpressions;
13: using System.Threading.Tasks;
14: using Windows.Web.Syndication;
15:
16: /// <summary>
17: /// Defines the class which handles retrieval of RSS feeds.
18: /// </summary>
19: internal class RangersNewsFeed
20: {
21: /// <summary>
22: /// The closing paragraph break tag
23: /// </summary>
24: private Regex closingParagraphBreakTag = new Regex("</?[pP]>");
25:
26: /// <summary>
27: /// The line break tag
28: /// </summary>
29: private Regex lineBreakTag = new Regex("</?[bB][rR]/?>");
30:
31: /// <summary>
32: /// The tag remover regex
33: /// </summary>
34: private Regex tagRemoverRegex = new Regex("<.*?>");
35:
36: /// <summary>
37: /// Initializes a new instance of the <see cref="RangersNewsFeed"/> class.
38: /// </summary>
39: public RangersNewsFeed()
40: {
41: this.Items = new SortedSet<NewsStory>(new NewsStoryComparer());
42: }
43:
44: /// <summary>
45: /// Gets or sets the items.
46: /// </summary>
47: /// <value>The items.</value>
48: public SortedSet<NewsStory> Items { get; set; }
49:
50: /// <summary>
51: /// Loads the items async.
52: /// </summary>
53: /// <returns>Task.</returns>
54: public async Task LoadItemsAsync()
55: {
56: this.Items.Clear();
57: var client = new SyndicationClient();
58: var tasks = (from url in App.Database.NewsUrls
59: select client.RetrieveFeedAsync(url).AsTask()).ToList();
60:
61: while (tasks.Count > 0)
62: {
63: var nextTask = await Task.WhenAny(tasks);
64: if (nextTask.Status == TaskStatus.RanToCompletion)
65: {
66: this.ParseSyndicationFeed(nextTask.Result);
67: }
68:
69: tasks.Remove(nextTask);
70: }
71: }
72:
73: /// <summary>
74: /// Cleanups the specified content.
75: /// </summary>
76: /// <param name="content">The content.</param>
77: /// <returns>System.String.</returns>
78: private string Cleanup(string content)
79: {
80: var result = this.lineBreakTag.Replace(content, Environment.NewLine);
81: result = this.closingParagraphBreakTag.Replace(result, Environment.NewLine);
82: result = this.tagRemoverRegex.Replace(result, string.Empty);
83: result = result.Replace("&", "&");
84: return result.Trim();
85: }
86:
87: /// <summary>
88: /// Parses the syndication feed.
89: /// </summary>
90: /// <param name="syndicationFeed">The syndication feed.</param>
91: private void ParseSyndicationFeed(SyndicationFeed syndicationFeed)
92: {
93: foreach (var item in syndicationFeed.Items)
94: {
95: this.Items.Add(new NewsStory()
96: {
97: Id = item.Id,
98: Title = item.Title.Text,
99: Published = item.PublishedDate.DateTime,
100: Author = item.Authors.Aggregate<SyndicationPerson, string>(
101: string.Empty,
102: (current, next) =>
103: {
104: if (current.Length > 0)
105: {
106: current += ", ";
107: }
108:
109: current += next.NodeValue;
110:
111: return current;
112: }),
113: Content = this.Cleanup(item.Summary.Text),
114: Link = item.Links[0].Uri
115: });
116: }
117: }
118: }
119: }
Question Time
Dev Lead question time …
Q: What, if anything, was a challenge with the News feature and why? |
||
Robert MacLean, our dev lead, replies … Nothing :) Windows Store Apps really treat the web as a first class citizen so connecting to websites, grabbing RSS etc... are all built in so it is super easy to work with. Add to that the new async options and we can build some super slick piece of code like LoadItemsAsync, which does the loading of feeds but does it async & loads the content as fast as possible. Now if you asked me, what sections I think could have more work - the number on is Cleanup, which tried to cleanup the HTML for presentation & without a HTML to Text parser we have added some of our own logic but this is an area which could use a better parser than what we have got |
Next?
We will peek into the code tracks the progress through the treasure map to see if we can reveal a few more gems.
References
- ALM Readiness Treasure Map Sample Code
- ALM Readiness Treasure Map (Table of Content) TOC Post
- ALM Readiness Treasure Map Windows Store App
- Previous posts:
- Treasure Map under the bonnet (hood) #1 … Windows 8 Layout Controls
- Treasure Map under the bonnet (hood) #2 … Windows 8 Styling
- Treasure Map under the bonnet (hood) #3 … Windows 8 Layout Controls (Update)
- Treasure Map under the bonnet (hood) #4 … Data Binding
- Treasure Map under the bonnet (hood) #5 … Finishing touches, but not yet finished!