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.

NewsFeed_0

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.

NewsFeed_1

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.

image

To explore the News feature you need to visit two areas of the Windows Store App solution.

image
  1. The News folder contains three source files dedicated to news, whereby we have included a snapshot of RangersNewsFeed.cs below.
  2. The other source file of interest is App.xaml.cs which initiates the population of the News island, within the App class constructor as shown in the sample code extract below.

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.

image

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("&amp;", "&");
   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 …

image_thumb211_thumb_thumb2_thumb

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

Robert-MacLean-v3_thumb21_thumb_thum_thumb

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