Freigeben über


Building an End-to-End Windows Store App - Part 2: Integrating Cloud Services

In my previous post, I started building a Windows Store app using Visual Studio Express 2012 for Windows 8, and along the way integrated a few Windows features like live tiles and search.  These days, however, many applications benefit from consuming and integrating with a variety of backend services running in the cloud, and I’d like mine to do so, as well.

In this regard, one of the top areas of feedback we’ve received from developers is the desire for a turn-key set of services that can be easily consumed from their device apps, without building, deploying, and managing their own services. Addressing this need, today we announced a preview of the new Windows Azure Mobile Services.  The release includes client SDKs for Windows Store apps written in JavaScript, C#, Visual Basic, or C++, and provides capabilities around storage, push notifications, and more (including the ability to write JavaScript scripts that run on the server).

In this post, I’ll extend my application with a variety of forms of backend services, including utilizing Windows Azure Mobile Services.

Enabling Sharing

Beyond live tiles and search, another feature I want to enable in my app is sharing.  As described at https://msdn.microsoft.com/library/windows/apps/hh465261.aspx, Windows 8 enables apps to publish data for other apps to consume via the Share charm.  For example, if I want to enable emailing an interesting RSS post to a friend, I don’t need to build all of the UI and logic around email into my app.  Instead, I can just share out the relevant content, and an email client on my system (such as the Mail app) can register as a share target to handle that aspect of the operation.  In this fashion, I can incorporate services into my app via other apps.

To make this work, I add a few lines of code to my ItemDetailPage.xaml.cs file:

private DataTransferManager _dtm;

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    _dtm = DataTransferManager.GetForCurrentView();
    _dtm.DataRequested += ShareDataRequested;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
    _dtm.DataRequested -= ShareDataRequested;
    _dtm = null;
}

void ShareDataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    var toShare = (SampleDataItem)flipView.SelectedItem;
    if (toShare != null)
    {
        args.Request.Data.Properties.Title = toShare.Title;
        args.Request.Data.Properties.Description = string.Empty;
        args.Request.Data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(toShare.Content));
    }
}

This registers a delegate for the DataRequested event on the current DataTransferManager.  When a request comes in, it populates some properties about the data being shared into the DataRequestedEventArgs structure, and then stores the data itself.  In this case, that data being stored is HTML markup, so I first use the HtmlFormatHelper.CreateHtmlFormat method from WinRT to ensure all of the right headers and footers are on the content, and then I use the SetHtmlFormat method to send the data to the target.  Windows handles the rest.

Here, you can see the result. While on the Items page, I’ve used the Share charm to share one of my blog posts, selecting the Mail app as the target:

Enabling Roaming

I don’t want to force a user of my app to re-enter their favorite blog feeds every time they run the app.  Rather, I want to be able to persist the information from run to run, such that feed URLs they loaded in a previous run are made available automatically for a subsequent run.  Moreover, if a user has multiple Windows devices with my app installed, I don’t want them to have to re-enter the same feeds on every device.  I instead want these favorites to roam with the user, available on whatever device they’re on. 

In the past, such a feature would require implementing a custom backend service of some kind for the application to talk to.  With Windows 8 and WinRT, however, we get integration with such services built-in. As described in the blog post at https://blogs.msdn.com/b/windowsappdev/archive/2012/07/17/roaming-your-app-data.aspx, application data can be roamed using the ApplicationData class and the collection returned from ApplicationData.Current.RoamingSettings.Values.  Simply store a key/value pair into Values, and it’ll be made available wherever your apps runs.

To take advantage of this, I add two methods to my app (in a new PersistedState.cs file), one to serialize my collection of feeds into RoamingSettings, and another to deserialize and return the data from RoamingSettings:

internal static void SaveFeedUrls()
{
    var serializer = new DataContractSerializer(typeof(List<string>));
    var feeds = SampleDataSource.AllGroups.Select(g => g.UniqueId).ToList();
    using(var tmpStream = new MemoryStream())
    {
        serializer.WriteObject(tmpStream, feeds);
        ApplicationData.Current.RoamingSettings.Values["feeds"] = tmpStream.ToArray();
    }
}

internal static IEnumerable<string> LoadFeedUrls()
{
    if (!ApplicationData.Current.RoamingSettings.Values.ContainsKey("feeds"))
        return Enumerable.Empty<string>();

    var serializer = new DataContractSerializer(typeof(List<string>));
    using(var tmpStream = new MemoryStream(
        (byte[])ApplicationData.Current.RoamingSettings.Values["feeds"]))
    {
        return (List<string>)serializer.ReadObject(tmpStream);
    }
}

Then, in GroupedItemsPage.LoadState (in GroupdItemsPage.xaml.cs), I add whatever feeds had previously been persisted:

protected override async void LoadState(
    object navigationParameter, Dictionary<string, object> pageState)
{
    this.DefaultViewModel["Groups"] = SampleDataSource.AllGroups;
    foreach(var feed in PersistedState.LoadFeedUrls())
        await AddFeedAsync(feed);
}

And after successfully adding a new feed, I add a call to persist the current set:

if (await SampleDataSource.AddGroupForFeedAsync(feed))
{
    UpdateTile();
    PersistedState.SaveFeedUrls();
}

With that, feeds are successfully persisted and loaded for this user of my app, across their Windows devices.

Enabling a Custom Backend Service

In enabling sharing, I took advantage of services supported by other apps on my system.  And in enabling roaming, I took advantage of a service supported natively by WinRT to share data between a given user’s devices.  But what about sharing data between users?  For example, I want to enable a service that would allow a user’s feeds to be published for other users to find and bring into the app. 

To achieve this, I can utilize the new Windows Azure Mobile Services, which make it trivial to put and get data from the cloud (a short tutorial is available here).

First, I create a new Mobile Service through the Windows Azure portal:

Within seconds, I have a new Mobile Service ready and waiting for me to customize:

Through the portal UI, I then create a new “table” within my Mobile Service to store information about the feeds that are being shared by my app:

I don’t have to configure any schema for the table, as by default that’s taken care of for me automatically by the service as I add data into it.  One thing I do want to do, however, is take advantage of the Mobile Services ability to let me write server-side JavaScript that handles Insert, Update, Read, and Delete events.  In my case, I don’t need to store duplicate entries when the same feed URL is submitted more than once, so I write a basic “Insert” script to help filter those duplicates out:

function insert(item, user, request) {
    tables.getTable("SharedFeed").where( { Url : item.Url }).read({
              success : function(results) {
                  if (results.length > 0) {
                      request.respond(200, results[0]);
                  }
                  else {
                      request.execute();
                  }
              },
              error: function(err) {
                  console.error(err); 
                  request.respond(500, "Error"); 
              }
          });
}

This editing is accomplished using the in-browser editor in the Azure portal:

This “insert” function is called for me automatically whenever a new item is about to be inserted into the table.  My script then queries the SharedFeed table for all entries where the URL matches that of the feed which is being inserted.  If there’s already such an entry, the script just sends a response to the caller, treating the operation as a nop.  If, however, this URL doesn’t exist, the script then proceeds to execute the original request, which will actually add the new record.  Of course, if there’s an error, I want to be able to diagnose the issue, so I log it and then send back to the client a generic “Error” message so that I don’t leak any potentially sensitive information.  All logged information is available through the logs pane of my service:

With that, my service is entirely configured, and I now need to consume it into my app.  The easiest way to do this is via the Windows Azure Mobile Services SDK.  With the SDK installed, I use the Add Reference… dialog in Visual Studio to bring in the client-side library for the service.  The library is available as an Extension SDK through the Windows\Extensions category:

Now I can use the client-side APIs to interact with my Mobile Service.  First, any time a user loads a feed, I want to store it into the “SharedFeed” table I previously configured.  To do this, I define a SharedFeed type with three properties: Id, Title, and Url (Id will be the primary key):

[DataTable(Name="SharedFeed")]
public class SharedFeed
{
    [DataMember(Name="Id")]    public int Id { get; set; }
    [DataMember(Name="Title")] public string Title { get; set; }
    [DataMember(Name="Url")]   public string Url { get; set; }
}

In GroupedItemPage.xaml.cs, I then write the following method to insert a new record from my app into the service:

async void ShareFeedOnServerAsync(string title, string url)
{
    try
    {
        var table = App.MobileService.GetTable<SharedFeed>();
        await table.InsertAsync(new SharedFeed { Title = title, Url = url });
    }
    catch (Exception exc) { Debug.WriteLine(exc.Message); }
}

And I modify the AddFeedAsync method, this time to invoke ShareFeedOnServer in addition to updating my live tile and persisting the data:

if (await SampleDataSource.AddGroupForFeedAsync(feed))
{
    UpdateTile();
    PersistedState.SaveFeedUrls();
    ShareFeedOnServerAsync();
}

Every time a feed is added, it’s now persisted to my service automatically.  Here you can see the results in the Azure portal after loading several feeds into my app:

 

Now, I need to be able to show these records to a user to enable them to choose from this list of feeds.  To do that, I again use the Add Item… dialog in Visual Studio, just as I did for Search, but this time choosing to add a new Split Page screen to my app:

This new PopularFeeds.xaml file contains more XAML than I actually need.  As I’m not showing any “details” for each element, I can delete all of the UI associated with details:

  • the second column definition
  • the whole ScrollViewer related to viewing details for each item
  • all animation key frames related to anything to do with detail controls, such as itemDetail, itemDetailTitlePanel, and itemDetailGrid

To display the list results, I add to this file’s Page.Resources a small bit of new XAML, based on the Standard130ItemTemplate from the StandardStyles.xaml file, and I update all code in PopularFeeds.xaml that was referencing Standard130ItemTemplate or Standard80ItemTemplate to instead reference this new FeedResultsItemTemplate:

<DataTemplate x:Key="FeedResultsItemTemplate">
    <Grid Margin="6">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0" Margin="10,0,0,0">
            <TextBlock Text="{Binding Title}" 
                       Style="{StaticResource TitleTextStyle}" 
                       TextWrapping="NoWrap"/>
            <TextBlock Text="{Binding Url}" 
                       Style="{StaticResource CaptionTextStyle}" 
                       TextWrapping="NoWrap"/>
        </StackPanel>
    </Grid>
</DataTemplate>

I also modify the existing resultsListView with a few properties, such that the user can click on (but not permanently select) an element from the list:

IsItemClickEnabled="true"
ItemClick="ItemListView_Click”
SelectionMode="None"

To ensure that clicking an element will navigate back to the previous screen after having added the clicked feed added, I add the following code to the PopularFeeds.xaml.cs file:

async void ItemListView_Click(object sender, ItemClickEventArgs e)
{
    var url = ((SharedFeed)e.ClickedItem).Url;
    await SampleDataSource.AddGroupForFeedAsync(url);
    PersistedState.SaveFeedUrls();
    this.IsEnabled = false;
    this.Frame.GoBack();
}

From the new page’s code-behind, I also delete all of the methods except for its DetermineVisualState method (from which I delete the concept of the logicalPageBack), and rewrite its LoadState method to populate the results list with all of the entries from the SharedFeed table in my Mobile Service, but only showing those entries which I’ve not already loaded:

protected override async void LoadState(
    object navigationParameter, Dictionary<string, object> pageState)
{
    var items = 
        (await App.MobileService.GetTable<SharedFeed>().ToEnumerableAsync())
        .Where(item => SampleDataSource.GetGroup(item.Url) == null)
        .ToList();
    this.DefaultViewModel["Items"] = items;
}

With that, my Popular Feeds page is done, and navigating to it results in a screen that allows the user to easily select feeds that other users of my app are using.

On the main page of my app (the GroupedItemsPage.xaml file), I then need to create a simple way for a user to navigate to this popular feeds page.  To do that, I add a new button to the previously created AppBar (and as with the previous button I added to the AppBar, I uncomment the required button style in StandardStyles.xaml):

<Button x:Name="btnPopularFeeds" Click="btnPopularFeeds_Click"
    Style="{StaticResource DownloadAppBarButtonStyle}" />

And I add this button’s Click handler (in GroupedItemsPage.xaml.cs) to navigate to the popular feeds page:

private void btnPopularFeeds_Click(object sender, RoutedEventArgs e)
{
    this.Frame.Navigate(typeof(PopularFeeds));
}

Enabling Push Notifications

My “popular feeds” feature is almost complete.  Users of my app are now able to share their feeds with others, and easily add feeds used by others.  However, auser isn’t notified if other users add new feeds.  For that, I want to support push notifications, and with Visual Studio, Windows 8, and Windows Azure Mobile Services together, I’m able to add push notifications to my app with little effort.

First, I need to configure Mobile Services to support push notifications.  For that, I open my browser and navigate to https://manage.dev.live.com/build, going through the steps outlined there to register my app.  This entails filling out a short form with two pieces of information obtained from the Packaging section of my app’s Package.appxmanifest:

Upon clicking “I accept,” I’m presented with three pieces of information: a new package name, a client secret, and a package security identifier.  I copy the package name into the “Package name” field in my app’s Package.appxmanifest (in the manifest, I also change the “Toast capable” field to “Yes”), and I copy both the client secret and the package identifier into my Mobile Service’s “push” tab:

With the service configuration out of the way, I need to augment my service to be able to store information about connected clients so that it can push notifications to them. To do this, I create a new “Channel” table, following the same steps as I did before with my “SharedFeed” table.  I even copy over the same “Insert” script that weeded out duplicates, with the only change to the code being which table is queried.

With that, I’m set up to track clients, and I now need to be able to send them all notifications when a new feed is added.  To achieve that, I again edit the "insert" script for my SharedFeed table, adding a “sendNotifications” function and calling it from "insert" after a new item is inserted:

function sendNotifications(item) {
    tables.getTable("Channel").read({
        success: function(results)
        {
            results.forEach(function(channel) {
                push.wns.sendToastText04(channel.channelUri, {
                    text1: "New feed recommended!",
                    text2: item.Title,
                    text3: item.Url
                }, {
                    success: function(data) { console.log(data); }
                });
            });
        }
    });
}

function insert(item, user, request) {
    ...
                request.execute();
                sendNotifications(item);
    ...
}

When “sendNotifications” is called from the “insert” function, it gets all of the registered clients from the channels table, and for each, it sends a toast to each client that contains information about the new feed.

That’s all for the service side of things.  In my client, I need to register for notifications as well, pushing up to the service the Uri that Windows creates for me.  In my App.xaml.cs file, I add the following function, which I call from the constructor after all of the other initialization is done:

private async void RegisterForPushNotificationsAsync()
{
    try
    {
        var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

        var jo = new JsonObject();
        jo.Add("channelUri", JsonValue.CreateStringValue(channel.Uri));

        var table = App.MobileService.GetTable("Channel");
        await table.InsertAsync(jo);
    }
    catch(Exception exc) { Debug.WriteLine(exc.Message); }
}

This uses the PushNotificationChannelManager from WinRT to create a “channel” for my app.  I then need store this channel’s Uri into the Mobile Service table I previously created.  To do that, rather than create a new type to store the Uri (as I did for SharedFeed), I instead just use the untyped approach supported by the Mobile Services client: creating a new JsonObject, storing into it the channelUri property, and inserting that JSON into the service.

With that, my push notifications are all wired up.  Now, whenever a new item is added to the “SharedFeed” table on the service, my server-side "insert" function is called, which calls my "sendNotifications" function, which calls the built-in "sendToastText04" function, which pushes that item down to all clients:

I could take this further if I wanted to.  For example, while using the app, a user might be unnecessarily notified of feeds they’re already reading.  To address that, I could add the following code to my RegisterForPushNotificationsAsync method:

channel.PushNotificationReceived += (s, e) =>
{
    var node = e.ToastNotification
                .Content
                .SelectSingleNode(@"//text[@id=""3""]");
    e.Cancel = node != null && 
               SampleDataSource.GetGroup(node.InnerText) != null;
};

This event handler is invoked before Windows displays the toast (but only if this app is the foreground).  The code checks whether the XML references a data source that’s already been added, and if it does, it cancels the toast notification.  I could of course take this much further, for example storing the feeds by user into my service, and only even pushing to clients that didn’t previously register that feed.

Note, too, that I did not configure any form of security for my service.  Windows Azure Mobile Service provides rich support around identity and access control, but for the purposes of this post, I’ve chosen to omit any such coverage.

Enabling Advertisements

Now that my app is basically done, I want to do one more thing: enable advertisements.  For this, I’ll utilize the Microsoft Ads SDK for Windows 8.

First, I navigate to https://advertising.microsoft.com/windowsadvertising/developer and follow the simple steps detailed there, starting by installing the SDK, which takes just a few seconds. Once installed, I navigate to https://pubcenter.microsoft.com to register for an account and to register my application:

This entails not only providing a name and type for my app, but also defining an “ad unit,” with information such as the size of the ad and the category of my app:

With that done, my app is registered to display ads, and the system provides to me an Application ID and an Ad Unit ID that I just include in my app:

Having installed the SDK, upon returning to Visual Studio the AdControl is now ready for me to use:

I drag the control to the designer in Visual Studio, placing the XAML where I want it, and filling in my Application ID and Ad Unit ID as provided by the web site:

Now when I run my app, I see ads successfully displayed:

Conclusion

In this two-part post, I detailed building a simple Windows Store app that both integrated with Windows functionality and utilized multiple backend services.  There was little code I actually had to write, with much of the heavy lifting handled by Visual Studio, Windows 8, and Windows Azure.  Along the way, I was able to build a nice user experience, one that included live tiles, search, sharing contracts, roaming data across devices, sharing data with other users, push notifications, and advertisements.

These posts highlight just some of the power provided to you by these tools and platforms, and I’m really excited to see what you build with them.

Namaste!

Comments

  • Anonymous
    August 28, 2012
    This is a great series!

  • Anonymous
    August 29, 2012
    It is a great series to demonstrate the ugly Visual Studio UI.

  • Anonymous
    August 29, 2012
    Great posts, really easy to follow and shows a clear approach to getting started!

  • Anonymous
    August 29, 2012
    Alas, I can't develop for Windows 8 because I have to support XP. Because of the lack of backwards compatability, I can't install .NET 4.5.  Which means I have to choose between Windows 8 and Windows XP.  Windows 8 could be fun.   But my job pays me to support XP.  Sadly I have to go with the job.

  • Anonymous
    September 09, 2012
    Hi, I have an offline machine I have installed Win8/VS2012 on to try out building an app. How do I go about getting a "Windows 8 developer license" while offline? It fails due to no internet connectivity. According to this Connect entry, this was to be fixed by RTM : connect.microsoft.com/.../cannot-request-offline-developer-license-for-visual-studio-2011-beta-in-windows-8-consumer-preview If this is not possible, then it represents a serious flaw in the model, as the organisation I work for is very particular about what devices can be internet connected (XP only).

  • Anonymous
    September 10, 2012
    @LachLan01: There is no mechanism for offline acquisition of a developer license at this time.

  • Anonymous
    September 10, 2012
    Just wanted to say that there are many of us who stand with you and the DevDiv against the SS (WinDiv). Keep the good work, and everything you've done (WPF, SL) is appreciated. Steve Ballmer should fix this internal war or step out.

  • Anonymous
    September 11, 2012
    The comment has been removed

  • Anonymous
    September 12, 2012
    As usual, the hard questions get ignored :(

  • Anonymous
    September 20, 2012
    The comment has been removed

  • Anonymous
    November 25, 2013
    great