Cloud Zone is brought to you in partnership with:

G. Andrew Duthie, aka devhammer, is a Technical Evangelist for Microsoft's Mid-Atlantic States district, where he provides support and education for developers working with the Microsoft development platform. In addition to his work with Microsoft, Andrew is the author of several books on ASP.NET and web development, and has spoken at numerous industry conferences from VSLive! and ASP.NET Connections, to Microsoft's Professional Developer Conference (PDC) and Tech-Ed. G. Andrew is a DZone MVB and is not an employee of DZone and has posted 41 posts at DZone. You can read more from them at their website. View Full User Profile

Building Back-end Data and Services for Windows 8 Apps: OData - Part 2

12.07.2012
| 4903 views |
  • submit to reddit

In part 1 of this post , I showed how to create a SQL database in Windows Azure, create a schema for adding leaderboard functionality to a game, create an Entity Framework model for the database, and then create and test a WCF Data Service on top of the model that provides a rich REST-style interaction model with great query support via OData. If you have not yet read part 1, you should do so before continuing.

And if you want to follow along, but don't yet have a Windows Azure account, you you can sign up for a 90-day trial that will give you everything you need.

If you'd like some back story on the games I'm planning to use to demonstrate the leaderboard service, check out this post.

In this second part, I'll show you:

  1. How I deploy the OData service to Windows Azure
  2. How I wire up my Windows 8 JavaScript games, Space Cadet and Catapult Wars, to the leaderboard service.

Deploying the Service

Now, I could start integrating the service into our games using the local version of the service, but since the service is pretty simple and I'm confident that it'll do what I want, let's go ahead and publish the service to Windows Azure, and use the published URI for the client app.

If you've never published a cloud service to Windows Azure, or it's been a while since you've used the Visual Studio tools for Azure, you'll be pleasantly surprised by how easy it is. All that's required is a one-time setup of the publishing credentials, which I'll start by right-clicking my Cloud project and selecting Publish…

AzurePublish1_thumb[2]

For the first-time I'm publishing, I need to map my project to the Azure subscription I want to publish to, and since I haven't yet set up a subscription, I'll click the "Sign in to download credentials link" to set that up first. That takes me to the following page on the Windows Azure site, which also prompts me to download a .publishsettings file containing a certificate that Visual Studio needs for the publishing process:

AzurePublish2_thumb[2]

Once I've downloaded the file (making note of where I saved it), I need to go back to Visual Studio, and click the Import…button, then browse to the .publishsettings file I downloaded in the previous step, and click the Open button. This should populate the drop-down with the new subscription information, so I can click Next. Since my subscription does not yet contain any cloud services to publish to, I'm prompted for a service name and a location where I'd like my service published, which I've populated in the dialog below:

AzurePublish3_thumb[2]

Once I click OK, I'm greeted with a page in the publishing wizard that allows me to specify the settings for my service, as shown below:

AzurePublish4_thumb[2]

Note that if I already had a service in production, I could change the Environment to Staging so that I could test the service prior to promoting it to Production. I can also choose to enable Remote Desktop access for my roles, if I want to have more control over the service configuration, or if I need to install any additional components for my service. There are additional settings under the advanced tab, but these are beyond the scope of this post. For my purposes, the defaults will do fine, so now I can either click Next to see a summary of the publish setttings I've chosen (and, if desired, save these as a profile for future use, which will enable faster future deployments), or just click the Publish button to publish my service.

When I click the Publish button, Visual Studio builds my project, and begins the deployment process, providing the status in the Windows Azure Activity Log window, as shown below:

AzurePublish5_thumb[2]

Once the log reports Complete, the Website URL above will be populated with the URL for the web role, where the service lives, in the form of http://servicename.cloudapp.net/. I'll click the link to test the service is active.

Unfortunately, the first time I tried this, I ran into the generic ASP.NET error screen (aka the yellow screen of death). If you run into this screen, you'll need to disable custom errors in web.config, and deploy a version of your cloud service using debug mode to see the detailed error information. Turns out that in my case, I had a conflict between the default version number for a WCF Service that was configured in the markup of the .svc file, and the version being loaded from the bin directory. After a quick bing search, I found this blog post, which discusses the process of bin deploying WCF Data Services, and provides a couple of different workarounds, one of which was simply removing the version information from the markup, which I did. Once I deployed the new version, my service was up and running just the same as it was when I ran it locally. Awesome!

Wiring up the Leaderboard Client App

One goal I have for this series of blog posts, and the accompanying data, is to make the leaderboard code as minimally intrusive to the basic game logic as possible. To that end, I'm planning to define all of the leaderboard logic in a single JavaScript file, with the functionality exposed to the client game using the WinJS namespace functionality, as in the following code example:

     WinJS.Namespace.define("Leaderboard", {

         setPlayerName: setPlayerName,

         init: init,

         addWin: incrementWins,

         addLoss: incrementLosses,

         addTie: incrementTies,

         updateScore: updateHighScore,

         getTopTenScores: getTopTenScores,

         getTopTenWins: getTopTenWinLossTie,

        leaderboardList: leaderboardList

    });


This allows me to expose a consistent API for leaderboard functionality, accessible by using the namespace Leaderboard, followed by the desired API member. So to initialize the leaderboard, I would call Leaderboard.init(args). A big advantage here is that the game itself doesn't have to know anything about the underlying implementation of the leaderboard logic, and it's pretty straightforward for me to change the internal logic of each of the functions to use a different back-end service without changing the code in the game itself (which is precisely what I'll be doing in future posts on using ASP.NET Web API and Windows Azure Mobile Services to provide similar back-end services).

Another neat thing is that once I've referenced the JavaScript file in the game's main HTML file (for example, in Dave Isbitski's Space Cadet, this would be default.html), Visual Studio will automatically provide IntelliSense statement completion for my API, including the arguments required by a given function, as shown below:



Additionally, the IntelliSense lookup displays a comment which appears on the line prior to the function for that API as the description for the function. Makes it very convenient to add some simple documentation for your API.

As you can see from the code listing above, the API supports two basic types of score tracking…high score and win/loss/tie record. For the latter, we have functions to add a win, loss, or tie to the existing record, and for high score, we have a function to update the player's score (assuming it's higher than the existing high score for that player). Then, since we might want to display the current leaderboard information, we have a couple of functions that return the list of high scores, or the Win/Loss/Tie records for the current game. Both of these functions update an internal variable named leaderboardList, which is exposed as an API member with the same name.

Initializing the Leaderboard


I'll start by declaring most of my variables up-top. Since JavaScript uses something called hoisting, which treats variable declarations anywhere within a given scope as if they were declared at the top of the function…BUT if the line where the variable is declared also includes initialization, the initialization is not hoisted, which can result in some tricky situations to debug. It's a good practice to declare all variables at the top of the function scope in which they're used:
     var leaderboardClient, xhrOptions, leaderboardList, scores, playerName, 

         gameName, currentPlayerScore, diag;

Next comes the init function, which the game provides with the game and player names, and which checks to ensure that at least one score record exists and if not, intializes the leaderboard with a blank score record for that player/game combo. Note that in the interest of simplicity, I have not added any code to ensure that the game/player combo is unique across all possible clients of the leaderboard service.
     // player and game are required for initialization

     function init(player, game) {

         return new WinJS.Promise(

             function (completed, error, progress) {

                 if (player) {

                     playerName = player;

                 }

                 else {

                     error("Player Name is required for initialization");

                }

     

                if (game) {

                    gameName = game;

                }

                else {

                    error("Game Name is required for initialization");

                }

    

                // replace with the URI for your custom service

                leaderboardClient = {

                    baseUri: "http://[YOUR SERVICE URI]/GameLeaderServiceOData.svc/Scores",

                    query: "?$filter=Game eq '" + gameName + "' and Player eq '" + playerName + "'"

                };

     

                // get score record for the current game/player

                xhrOptions = {

                    url: leaderboardClient.baseUri + leaderboardClient.query,

                    headers: {

                        "Accept": "application/json;"

                    }

                };

                WinJS.xhr(xhrOptions).done(function (results) {

                    var gameScore;

                    scores = JSON.parse(results.responseText);

                    if (!scores.value[0]) {

                        // create initial leaderboard entry

                        gameScore = {

                            Game: gameName, Player: playerName, Score1: 0, Wins: 0, Losses: 0, Ties: 0

                        };

                        xhrOptions = {

                            data: JSON.stringify(gameScore),

                            headers: {

                                "Content-type": "application/json; charset=utf-8"

                            },

                            type: "post",

                            url: leaderboardClient.baseUri

                        };

                        WinJS.xhr(xhrOptions).done(function (results) {

                            completed();

                        },

                        function (e) {

                            error(e);

                        });

                    }

                    else {

                        completed();

                    }

                },

                function (e) {

                    error(e);

                });

            });

    }


 
In this function, I'm showing a slight variation on the promise pattern. This time, I declare a variable called scorePromise that is passed the result of calling getCurrentPlayerScore. In line 3, I then call .done, passing in the function I want called as usual. This code just makes what's happening (getCurrentPlayerScore returns a Promise object) a little more explicit, and may aid readability of my code. In line 4 above, I check to see if the new score passed into the function is higher than the score returned by getCurrentPlayerScore via the promise object, and if so, call updateScoreRecord, passing it the currentScore object.

NOTE: In the code above, you might notice that I'm checking "Score1", when you might expect from the schema I showed in part 1 of this post, which had a column named "Score," that the property should be "Score". Well, this happened because of the way that I named the table ("Scores") and column ("Score"), and the fact that in the process of building the Entity Framework model, the default is to singularize the entity names, so the entity in the model representing the Scores table is called "Score". Because you can't have an entity with a property name that is the same as the entity name, Entity Framework helpfully renamed the property "Score1" and WCF Data Services provides the same property on the Score object.

Here's the code for updateScoreRecord:
     function updateScoreRecord(newScoreRecord) {

         xhrOptions = {

             data: JSON.stringify(newScoreRecord),

             headers: {

                 "Content-type": "application/json; charset=utf-8",

                 "X-HTTP-Method": "MERGE"

             },

             type: "post",

             url: leaderboardClient.baseUri + "(" + newScoreRecord.Id + ")"

        };

        WinJS.xhr(xhrOptions).done(function (results) {

            showMessage("Leaderboard Updated.");

        },

        function (e) {

            showMessage("Leaderboard could not be updated.");

        });

    }

This function takes the score record passed in from updateHighScore, prepares it for submitting using JSON.stringify, and then make the result the data property of the xhrOptions object. I also pass in a Content-type header to specify that I'm sending JSON data, as well as the X-HTTP-Method header with the value of "MERGE". This tells the OData service to update the specified record with any properties provided, while leaving any unspecified values unchanged. You can read more about updating records via OData here.

Updating the high score from the game code is fairly straightforward, requiring only passing in the score to update:
    Leaderboard.updateScore(score);

That's all that's needed to wire up the functionality to store and update score records for a given game (in this case, Space Cadet). For Space Cadet, and similar games that allow the player to change their player name, I also added a function to update the player name, and there are functions to increment the Wins, Losses, and Ties properties for games that keep score using those values instead of a numeric high score. And for Catapult Wars, I can use the exact same JavaScript library. The only change needed is to call Leaderboard.addWin or Leaderboard.addLoss, depending on whether the player wins or loses the game.

Adding the Leaderboard Page


Of course, having all this leaderboard info doesn't do me a lot of good if I don't show it off every now and then, so to remedy that, I added a folder to my project called Leaderboard, and added to it a new Page control (which adds a matched set of HTML, CSS, and JavaScript files) called Leaderboard.html. Here's what the markup looks like for Space Cadet:

     <!DOCTYPE html>

     <html>

     <head>

         <meta charset="utf-8" />

         <title>Leaderboard</title>

      

         <!-- WinJS references -->

         <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

         <script src="//Microsoft.WinJS.1.0/js/base.js"></script>

        <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

     

        <link href="leaderboard.css" rel="stylesheet" />

        <script src="/js/leaderboardOData.js"></script>

        <script src="leaderboard.js"></script>

    </head>

    <body>

        <div id="myTemplate" data-win-control="WinJS.Binding.Template">

            <div>

                <div class="win-type-x-large" style="width: 400px;">

                    <span>Player: </span><em><span data-win-bind="textContent: Player"></span></em>

                </div>

                <div class="win-type-large">

                    <span>Score: </span><em><span data-win-bind="textContent: Score1"></span></em>

                </div>

            </div>

        </div>

        <div class="pagecontrol">

            <header aria-label="Header content" role="banner">

                <button id="backButton" class="win-backbutton" aria-label="Back" type="button"></button>

                <span id="title" class="win-type-xx-large pagetitle titlearea win-type-ellipsis">Leaderboard</span>

            </header>

            <section aria-label="Main content" role="main">

                <div id="leaderListView" data-win-control="WinJS.UI.ListView" 

                    data-win-options="{itemTemplate : select('#myTemplate'), selectionMode: 'none'}"></div>

            </section>

        </div>

    </body>

    </html>

It simply sets up a WinJS.UI.ListView control that I'll use for databinding the score results from the service, with a template that displays each player name along with their score. There's also a back button to return the player to the game once they're done looking at the leaderboard.

Here's the accompanying JS code:

     // For an introduction to the Page Control template, see the following documentation:

     // http://go.microsoft.com/fwlink/?LinkId=232511

     (function () {

         "use strict";

      

         var appdata = Windows.Storage.ApplicationData;

         var playerName = appdata.current.roamingSettings.values["playerName"]

         var gameName = "Space Cadet"

      

        WinJS.UI.Pages.define("/leaderboard/leaderboard.html", {

            // This function is called whenever a user navigates to this page. It

            // populates the page elements with the app's data.

            ready: function (element, options) {

                // TODO: Initialize the page here.

                document.getElementById("backButton").addEventListener("click", goBack, false);

                Leaderboard.init(playerName, gameName);

                Leaderboard.getTopTenScores().done(

                    function (leaderList) {

                        title.textContent = gameName +" Top 10";

                        leaderListView.winControl.itemDataSource = leaderList.dataSource;

                    }

                );

            },

     

            unload: function () {

                // TODO: Respond to navigations away from this page.

            },

     

            updateLayout: function (element, viewState, lastViewState) {

                /// <param name="element" domElement="true" />

     

                // TODO: Respond to changes in viewState.

            }

        });

     

        function goBack() {

            location.href = "/default.html";

        }

     

    })();

Note that for Space Cadet, I'm leveraging the roaming settings support in Windows 8, which means that wherever I have the game installed, my player name will follow me, as long as I'm logged in with the same Microsoft Account. In the ready function, I first bind the click event of the back button to the goBack function, which simply reloads default.html. Next, I call Leaderboard.init (to ensure the leaderboard has been properly initialized for the game), and then call Leaderboard.getTopTenScores, and provide a function to be called when this operation is complete. In that function, I set the title of the leaderboard page to the name of the game plus "Top 10", and finally set the itemDataSource of the ListView control to the dataSource property of the WinJS.Binding.List passed in to the function from getTopTenScores. Add a little CSS for formatting, and here's the result:


Nothing fancy, I'll admit, but it gets the job done. Here's the code for the getTopTenScores function:
     function getTopTenScores() {

         return new WinJS.Promise(

             function (completed, error, progress) {

                 // get top ten scores for the current game

                 xhrOptions = {

                     url: leaderboardClient.baseUri + "?$filter=Game eq '" + gameName + "'&$orderby=Score1 desc&$top=10",

                     headers: {

                         "Accept": "application/json;"

                     }

                };

                WinJS.xhr(xhrOptions).done(function (results) {

                    scores = JSON.parse(results.responseText);

                    if (!scores.value[0]) {

                        // throw exception...no score found

                        error("No score record found!");

                    }

                    else {

                        leaderboardList = new WinJS.Binding.List(scores.value);

                        completed(leaderboardList);

                    }

                },

                function (e) {

                    showMessage(e);

                });

            });

    }

A couple of things to note are the use of the $orderby and $top query parameters, which help organize the data in the way I need it for the leaderboard (you might also notice that I don't have a full 10 records in my database yet, but if I had more than 10 records, this query would only return the top 10), and that I'm not using the player name in the query this time, since I actually want to get scores for all players for this game.

Again, I'm returning a WinJS.Promise object, and if the xhr operation is successful, I create a new WinJS.Binding.List object based on the scores retrieved, and pass it to the completed function, which allows the Leaderboard.html page to bind it to the ListView control on the page. The leaderboard page for Catapult Wars looks almost identical, but in this case, I'm calling Leaderboard.getTopTenWins, and binding the Wins property in the ListView:


Wrap-up

So in this pair of posts, you've seen how I created a new SQL database in Windows Azure, defined a schema for the data, used Entity Framework to create a model of the data, and wrapped that in a WCF Data Service, providing a powerful, but simple URL-based query syntax that I can use to interact with my service. You've also seen how I created a single JavaScript library that used the WinJS namespace functionality to expose a simple API that can be used by multiple games to interact with the leaderboard service.

As noted in the overview post for this series, there are both advantages and disadvantages to this approach to building back-end services:

Advantages
Mature platform – SQL Server, Entity Framework, and WCF Data Services have each been around for numerous versions, and have had time to improve and grow over that time. Maturity also means greater familiarity for .NET developers.
Data format options – can return data in XML (ATOM) or JSON format.
Customizable – implementing custom logic can be done in a variety of ways, including attributes, custom code in the WCF data service, and exposing stored procedures as Service Operations.
Query flexibility – thanks to OData's powerful query syntax, requesting and submitting data is easily accomplished by building the appropriate URL and making simple HTTP requests.

Disadvantages
Complexity – as you might have noticed from the length of this blog post, there are quite a few steps to getting this set up, and a number of layers. While this means more points at which the service may be customized, it also increases the overall complexity of the solution.
No client library for JavaScript apps – while there is direct support in XAML-based Windows Store apps for working with OData services, there is no official support for OData services in JavaScript apps. As you have seen, it's pretty easy to build xhr requests that access the service, but this approach may seem rather foreign to anyone who's used an OData client library, many of which use LINQ-style syntax for performing operations on the service, treating the service more like a local object, and which hide the underlying HTTP complexities from the developer.

So is this the right approach for your app? The answer, of course, is "it depends." You can certainly build a robust, scalable, and easy-to-use service back-end for your Windows Store app or game using the techniques I've illustrated here. But there are other, simpler approaches that you may want to consider as well.

What's Next?


In the next part of this series, I'll show another approach to building back-end services, as I take on building this same game leaderboard service using the new ASP.NET Web API.

While you're waiting, consider signing up for Generation App. There are lots of great resources available for building Windows 8 apps (and now for Windows Phone 8 as well). It's free, and you control how often updates are sent, so there's no good reason to pass it up. Sign up now!


Published at DZone with permission of G. Andrew Duthie, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)