In this series, I’m exploring a variety of ways to build back-end data storage and services for Windows 8 apps (many of which, BTW, can also be used for other mobile and web apps as well). Here are the posts so far:
- Overview – High-level view of some of the available platform technologies, and a discussion of the game leaderboard scenario I’m using to demonstrate them, as well as the games I’m using for my demos.
- Building Back-end Data and Services for Windows 8 Apps: OData – Part 1 – Creating the database and schema, using Entity Framework to model the data store, and using WCF Data Services to expose the model via as a RESTful service with rich queryability.
- Building Back-end Data and Services for Windows 8 Apps: OData – Part 2 – Deploying the service to Windows Azure, and wiring up the leaderboard service to the games, using a custom JavaScript library with a well-defined API.
Overview
In this post, I’m going to show you how I can implement the same leaderboard service using a relatively new member of the ASP.NET stack, Web API. Web API is designed specifically for building services that are accessed via HTTP, and is a lightweight, yet highly customizable way of building RESTful services, and even supports OData as well. I’ll also show you how easy it is to host services built using ASP.NET Web API using the new Windows Azure Web Sites feature.
For the sake of simplicity, I’m going to leverage the same database schema that I created in part 1 of the WCF Data Services-based solution, and will also leverage Entity Framework for modeling the data. If you have not already, you should read through the sections entitled “Creating the Database” and “Creating the Schema” in that post, before moving on. I’ll wait.
Preparing a Home for our Web API
Since the last project showed how I was able to host a WCF Data Services-based OData service using a Windows Azure cloud service, this time I’m going to do things a little differently and host my Web API using Windows Azure Web Sites, a new offering (still in preview as of the time of this writing). As with cloud services, you will need a Windows Azure account to follow along. If you don’t have one, you can get a 90-day free trial account.
Creating a Web Site in Windows Azure is simple. I just log into the management portal, and click on the Web Sites tab. Since I don’t have any web sites yet, this is what I’ll see:
Next, I’ll click the link to create a web site, which prompts me with some options for how I’d like to create my web site:
Note the reminder that the service is (as of this writing) in preview, and the options for quick create, creating with a database, and creating from the gallery (a great option for when you want to stand up a site based on one of the many web apps available from the Windows Web App Gallery, such as DotNetNuke, Umbraco CMS, Orchard CMS, or WordPress). I’m going to choose Create with Database. This prompts me with the following dialog, which I’ve filled out with my desired web site URL (note that I’m using the free version of the service, which uses the naming pattern [mysitename].azurewebsites.net. (NOTE: You can configure your web site to use a custom domain name, but this requires switching the site to Shared or Reserved mode, which may have cost implications) Additionally, I’m specifying that I want to create the site in the East US region, and create a new database. I’ve left the connection string name as the default:
When I click the arrow for “next,” I see page 2 of the Create Web Site wizard, where I provide the database settings for the new database (I’m using the same database server as for my previous project, so my login information remains the same):
Next, I’ll click the checkmark, and let Windows Azure spin up my new website. It takes just a few minutes, and then I’m all set.
Creating a Web API Project
I’ll start off similarly to the WCF Data Services project, using Visual Studio 2012, but this time, I’ll start with an ASP.NET MVC4 Web Application using Visual C#. I’m calling the project GameLeaderWebAPI (note that I’ve specified .NET Framework 4.5 as the version for my project):
Once I click OK, I’m greeted with a dialog that prompts me to select the specific ASP.NET MVC 4 template I want to work with. If I wanted to create a full MVC site with a Web API service, I’d select the Web API template, but for my current purposes, that template has a number of things I don’t need (like views, for example), so I’m going to start with the Empty template, as shown below:
Since I’m not planning on having any views, it doesn’t really matter which view engine I choose, so I’ve left the default. When I click OK, Visual Studio creates the project for me, with folders appropriate for MVC, along with all the necessary configuration for my Web API, but without all of the default controllers and views I’d get with the Web API template. Here’s what the project looks like in Solution Explorer at this point:
Before I start adding any models or API controllers, I want to make sure I’m ready to support OData in my Web API, which is currently accomplished through a nuget package. To get the package, I’ll open up the Nuget Library Package Manager using Tools > Library Package Manager > Manager Nuget Packages for Solution… (note that at the time of this writing, the Web API OData package is in alpha release, so you’ll need to choose “Include Prerelease” in the leftmost drop-down, as shown below, to see the package) and search for “Web API OData”. Once I find the package, I simply click the Install button and click through the prompts to finish the install:
Now, it’s important to note that you don’t need to do this to create a RESTful service with Web API. But since my previous example leveraged OData for its rich URL query functionality, I’d like to maintain that in my Web API version.
Adding the Model
Now that the project is set up, I want to add a model, and connect it to my back-end database. As noted earlier, I’m planning to use the same schema as in the previous post on WCF Data Services, so before adding the model, I created a table named GameScores in the database I created earlier, and added columns for Id, Game, Player, Score, Wins, Losses, and Ties. Note that in my WCF Data Services version, I had named the table Scores, which didn’t play nicely with Entity Framework, due to the fact that the table had a column name that was the singular version of the table name, which caused EF to rename the column to Score1, which was a little confusing when querying, to say the least. With the schema in place, I can simply add a new ADO.NET Entity Model to the Models folder, and point it at my database (again, see the previous post for details on this) and my model is ready to go:
I’ve seeded the GameScores table with some data to make the remaining tasks easier. I did so manually, but if you want to automate the process (for example if you want to use Entity Framework Migrations), Entity Framework supports seeding your tables automatically when you update them (read more about Entity Framework here).
Defining the API
Now that my model is in place, it’s time to create the API, which is done by simply adding an API Controller, which is a specialized type of MVC controller specifically for Web API. It’s how we create the endpoints and logic to handle all of the HTTP requests for our API. Visual Studio 2012 provides great scaffolding support for automating the process of creating an API Controller, but I need to build the project in order for the scaffolding to find my model class. Once the project is built, I’ll just right-click the Controllers folder, and select Add > Controller… and specify the Model class and Data context class, as well as the name for my controller:
Note that if you see an error message indicating that no Model classes are available, you need to build the project and try adding the controller again.
When I click Add, Visual Studio scaffolds the API Controller for me, including creating the underlying data context and creating functions for the basic CRUD operations, and my service is ready to test. But if I click the start button in the toolbar to open the site in Internet Explorer, here’s what I see:
Oops! Turns out, there’s a good reason for this. Remember how I started this project using the Empty template? Well, there aren’t any views associated with the project, so there’s nothing for me to see at the root URL. Thankfully, the API Controller that I scaffolded helpfully includes comments with the correct path to append to get to a given API member, as you can see below for the GetGameScores function:
1: // GET api/Scores
2: public IEnumerable<GameScore> GetGameScores()
3: {
4: return db.GameScores.AsEnumerable();
5: }
If I append api/Scores to the URL shown above, I’ll be prompted to open or download a file named Scores.json, which contains the response from GetGameScores (which defaults to JSON format, though Web API supports content negotiation as a means of supporting additional formats).
We’re not quite done with the API, however…recall that I wanted to add some basic OData support as well.
Adding OData Support
You might think, given how simple the function GetGameScores is, that we have to do a lot of tweaking to make our API support OData query syntax. In truth, the basic level of support is super-easy to add. All I have to do is modify the GetGameScores function as follows:
1: // GET api/Scores
2: [Queryable]
3: public IEnumerable<GameScore> GetGameScores()
4: {
5: return db.GameScores.AsEnumerable();
6: }
If you blinked, you might have missed the change. All I needed to do was add the [Queryable] attribute (which is provided by the Nuget package I installed earlier), and now my API supports OData URL queries like $filter, $orderby and more. So a query like this:
http://localhost:55830/api/Scores?$orderby=Score
returns all scores ordered by score (ascending by default…just tack on a space and “desc” to get descending results…note that if you’re doing this in the browser, you should use %20 for the space). And this query:
http://localhost:55830/api/Scores?$filter=Game%20eq%20’Space Cadet’
returns all scores for the game “Space Cadet”.
IMPORTANT:
The Queryable attribute, as shown above, allows all supported query operations by default. Since some of these operations could potentially be used to attempt to mount DOS attacks, by sending complex queries to your service. As such, you should consider limiting the types of queries you allow using some simple properties you can add to the Queryable attribute, as shown in this blog post.
And with that, my Web API service is good to go…on to deployment.
Publishing a Site to Windows Azure
As with the example I build on WCF Data Services, I could start integrating my service with the games running the service locally, but I’m going to go ahead and publish the site first, so that I can use the Windows Azure version of the URL for the service.
To publish, all I need to do is right-click the project in Solution Explorer, and select Publish…, which will open the following dialog:
I’m going to import my publishing profile, which comes in the form of a .PublishSettings file, but first I need to download this from the Windows Azure management portal page for my website, by clicking the link highlighted below in the “quick glance” section of the Web Sites page for my Azure web site:
Once I’ve saved the file to my PC, I can go back to the Publish Web wizard and import it by clicking the Import button, selecting the file, and clicking the Open button. This will import the settings for my web site. I can click Next to view additional settings, including information on my database, and click Publish to publish my website to Windows Azure.
Using the API
Once published, I can simply make HTTP requests to the URL for my website, using the following URLs and HTTP verbs:
Getting all Scores
http://[myAzureWebsiteUrl].azurewebsites.net/api/scores – HTTP GET request
where “myAzureWebsiteUrl” is the unique portion of the URL for the published site.
Note that we can also use OData queries to return a single unique score, assuming we have the game and player name we’re looking for, and the game only stores one unique score per player.
Getting a Single Score
http://[myAzureWebsiteUrl].azurewebsites.net/api/scores/5 – HTTP GET request
where 5 is the ID of the score record to return.
Updating a Score
http://[myAzureWebsiteUrl].azurewebsites.net/api/scores/5 – HTTP PUT request
Note that the URL is the same as getting a single score, with the last portion being the ID of the score to update, we simply change to using a PUT request, and provide the score object in the body of the request (by default, the API accepts JSON, but you can update it to support additional formats).
Inserting a Score
http://[myAzureWebsiteUrl].azurewebsites.net/api/scores – HTTP POST request
The URL is the same as for getting all scores, but uses the POST verb instead of GET. Again, we need to pass the score object in the body of the request as JSON.
Deleting a Score
I won’t make use of this API in the game clients, but here’s how to delete a score entry:
http://[myAzureWebsiteUrl].azurewebsites.net/api/scores/5 – HTTP DELETE request
where 5 is the ID of the score record to delete.
Because everything in the API uses standard HTTP verbs, our API has great usability and fidelity to the way the web works.
Wiring up the Leaderboard Clients
Since I’ve already walked through once the process of wiring up the leaderboard service to the games, I’m going to make this a quicker version, and refer you back to part 2 of the WCF Data Services version for additional details.
To wire the games up to the Web API version of my leaderboard service, it made sense to start with the JavaScript file I created for the WCF Data Services version, and modify it as needed to work with the new service. The updates required very few changes to the code, and allowed me to maintain a completely compatible interface between the leaderboard JavaScript library and the games. As a reminder, here’s what the leaderboard JavaScript library exposes to the game client(s):
1: WinJS.Namespace.define("Leaderboard", {
2: setPlayerName: setPlayerName,
3: init: init,
4: addWin: incrementWins,
5: addLoss: incrementLosses,
6: addTie: incrementTies,
7: updateScore: updateHighScore,
8: getTopTenScores: getTopTenScores,
9: getTopTenWins: getTopTenWinLossTie,
10: leaderboardList: leaderboardList
11: });
By using the namespace support in WinJS, I can hide the internals of my service behind a consistent set of functions, which makes it easy for me to switch from one back-end service to another.
Here’s the full listing for the new leaderboard service library, which I’ve called leaderboardWebAPI.js (I’ve highlighted the changes, and will walk through them one by one below the listing):
1: (function () {
2: "use strict";
3:
4: // Leaderboard Client for ASP.NET Web API
5: // by G. Andrew Duthie
6: // This code is copyright © Microsoft Corporation, and licensed under the Microsoft Limited Public License (Ms-LPL).
7: // All rights reserved.
8: // Code is provided AS-IS with no warranties.
9:
10: var leaderboardClient, xhrOptions, leaderboardList, scores, playerName, gameName, currentPlayerScore, diag;
11:
12: WinJS.Namespace.define("Leaderboard", {
13: setPlayerName: setPlayerName,
14: init: init,
15: addWin: incrementWins,
16: addLoss: incrementLosses,
17: addTie: incrementTies,
18: updateScore: updateHighScore,
19: getTopTenScores: getTopTenScores,
20: getTopTenWins: getTopTenWinLossTie,
21: leaderboardList: leaderboardList
22: });
23:
24: // player and game are required for initialization
25: function init(player, game) {
26: return new WinJS.Promise(
27: function (completed, error, progress) {
28: if (player) {
29: playerName = player;
30: }
31: else {
32: error("Player Name is required for initialization");
33: }
34:
35: if (game) {
36: gameName = game;
37: }
38: else {
39: error("Game Name is required for initialization");
40: }
41:
42: // replace with the URI for your custom service
43: leaderboardClient = {
44: baseUri: "http://[myAzureWebsiteName].azurewebsites.net/api/scores",
45: query: "?$filter=Game eq '" + gameName + "' and Player eq '" + playerName + "'"
46: };
47:
48: // get score record for the current game/player
49: xhrOptions = {
50: url: leaderboardClient.baseUri + leaderboardClient.query,
51: headers: {
52: "Accept": "application/json;"
53: }
54: };
55: WinJS.xhr(xhrOptions).done(function (results) {
56: var gameScore;
57: scores = JSON.parse(results.responseText);
58: if (!scores[0]) {
59: // create initial leaderboard entry
60: gameScore = {
61: Game: gameName, Player: playerName, Score: 0, Wins: 0, Losses: 0, Ties: 0
62: };
63: xhrOptions = {
64: data: JSON.stringify(gameScore),
65: headers: {
66: "Content-type": "application/json; charset=utf-8"
67: },
68: type: "post",
69: url: leaderboardClient.baseUri
70: };
71: WinJS.xhr(xhrOptions).done(function (results) {
72: completed();
73: },
74: function (e) {
75: error(e);
76: });
77: }
78: else {
79: completed();
80: }
81: },
82: function (e) {
83: error(e);
84: });
85: });
86: }
87:
88: function getCurrentPlayerScore() {
89: return new WinJS.Promise(
90: function (completed, error, progress) {
91: // get score record for the current game/player
92: xhrOptions = {
93: url: leaderboardClient.baseUri + leaderboardClient.query,
94: headers: {
95: "Accept": "application/json;"
96: }
97: };
98: WinJS.xhr(xhrOptions).done(function (results) {
99: scores = JSON.parse(results.responseText);
100: if (!scores[0]) {
101: // throw exception...no score found
102: error("No score record found!");
103: }
104: else {
105: currentPlayerScore = scores[0];
106: completed(currentPlayerScore);
107: }
108: },
109: function (e) {
110: error(e);
111: });
112: });
113: }
114:
115: //updates the player name of the current player for this game - requires the name as a string
116: function setPlayerName(name) {
117: var namePromise = getCurrentPlayerScore();
118: namePromise.done(function (currentScore) {
119: if (playerName != name) {
120: playerName = name;
121: currentScore.Player = playerName;
122: xhrOptions = {
123: data: JSON.stringify(currentScore),
124: headers: {
125: "Content-type": "application/json; charset=utf-8",
126: },
127: type: "put",
128: url: leaderboardClient.baseUri + "/" + currentScore.Id
129: };
130: WinJS.xhr(xhrOptions)
131: .done(function (results) {
132: leaderboardClient.query = "?$filter=Game eq '" + gameName + "' and Player eq '" + playerName + "'";
133: showMessage("Player Name Changed.");
134: },
135: function (e) {
136: showMessage("Name could not be changed on leaderboard.");
137: }
138: );
139: }
140: });
141: }
142:
143: function incrementWins() {
144: var scorePromise = getCurrentPlayerScore();
145: scorePromise.done(function (currentScore) {
146: currentScore.Wins++;
147: updateScoreRecord(currentScore);
148: });
149: }
150:
151: function incrementLosses() {
152: var scorePromise = getCurrentPlayerScore();
153: scorePromise.done(function (currentScore) {
154: currentScore.Losses++;
155: updateScoreRecord(currentScore);
156: });
157: }
158:
159: function incrementTies() {
160: var scorePromise = getCurrentPlayerScore();
161: scorePromise.done(function (currentScore) {
162: currentScore.Ties++;
163: updateScoreRecord(currentScore);
164: });
165: }
166:
167: // update score - requires new score passed as number
168: function updateHighScore(newScore) {
169: var scorePromise = getCurrentPlayerScore();
170: scorePromise.done(function (currentScore) {
171: if (currentScore.Score <= newScore) {
172: currentScore.Score = newScore;
173: updateScoreRecord(currentScore);
174: }
175: });
176: }
177:
178: function updateScoreRecord(newScoreRecord) {
179: xhrOptions = {
180: data: JSON.stringify(newScoreRecord),
181: headers: {
182: "Content-type": "application/json; charset=utf-8"
183: },
184: type: "put",
185: url: leaderboardClient.baseUri + "/" + newScoreRecord.Id
186: };
187: WinJS.xhr(xhrOptions).done(function (results) {
188: showMessage("Leaderboard Updated.");
189: },
190: function (e) {
191: showMessage("Leaderboard could not be updated.");
192: });
193: }
194:
195: function getTopTenScores() {
196: return new WinJS.Promise(
197: function (completed, error, progress) {
198: // get top ten scores for the current game
199: xhrOptions = {
200: url: leaderboardClient.baseUri + "?$filter=Game eq '" + gameName + "'&$orderby=Score desc&$top=10",
201: headers: {
202: "Accept": "application/json;"
203: }
204: };
205: WinJS.xhr(xhrOptions).done(function (results) {
206: scores = JSON.parse(results.responseText);
207: if (!scores[0]) {
208: // throw exception...no score found
209: error("No score record found!");
210: }
211: else {
212: leaderboardList = new WinJS.Binding.List(scores);
213: completed(leaderboardList);
214: }
215: },
216: function (e) {
217: showMessage(e);
218: });
219: });
220: }
221:
222: function getTopTenWinLossTie() {
223: return new WinJS.Promise(
224: function (completed, error, progress) {
225: // get top ten scores for the current game
226: xhrOptions = {
227: url: leaderboardClient.baseUri + "?$filter=Game eq '" + gameName + "'&$orderby=Wins desc&$top=10",
228: headers: {
229: "Accept": "application/json;"
230: }
231: };
232: WinJS.xhr(xhrOptions)
233: .done(function (results) {
234: scores = JSON.parse(results.responseText);
235: if (!scores[0]) {
236: // throw exception...no score found
237: error("No score record found!");
238: }
239: else {
240: leaderboardList = new WinJS.Binding.List(scores);
241: completed(leaderboardList);
242: }
243: },
244: function (e) {
245: showMessage(e);
246: }
247: );
248: }
249: );
250: }
251:
252: function showMessage(msg) {
253: diag = new Windows.UI.Popups.MessageDialog(msg);
254: diag.showAsync();
255: }
256:
257: })();
Here are the changes I made from the WCF Data Services version:
- Line 44, in the init function (which initializes the leaderboard data for a given game), modified the baseUri property of the leaderboardClient object to reflect the updated URL for the service
- Line 58, updated the syntax by which I test for a valid return value from the xhr call, owing to differences in how WCF Data Services and Web API return the results
- Line 61, updated the name of the Score property of the gameScore object. In the previous version, this was Score1 due to the aforementioned conflict between my database table name and how EF deals with pluralizing/singularizing objects and their properties
- Lines 100 and 105, in the function getCurrentPlayerScore (which returns the first score for the given player and game), updated the syntax by which I test for and assign the value returned from the xhr call, again due to differences in how Web API returns the results.
- Lines 123, 127, 128, and 132, in function setPlayerName (only used by Space Cadet, to update the player name if required by the game), updated the way that I add the data to the xhr request, to include the entire object (the WCF Data Services version used a custom HTTP verb, MERGE, which allowed the service to update the score record with just the new playerName as the data). Also changed the HTTP verb to PUT, as required by Web API for updates, and modified the URL for the xhr request to match that of the Web API (i.e. – /api/scores/id). Also added code to update the query property of the leaderboardClient object to include the updated playerName, fixing a bug in the previous version of the library
- Lines 171 and 172, in function updateHighScore (which gets the current score record for the player/game, and calls another function to update the leaderboard), updated the property name from Score1 to Score, again, to match updates to my EF model
- Lines 184 and 185, in function updateScoreRecord (called by updateHighScore, and which sends the updated score to the service), changed the HTTP verb to PUT (removing the header that specified the custom MERGE verb), and modified the URL to reflect the syntax required by Web API
- Lines 200, 207, and 212, in function getTopTenScores, updated the naming of Score1 to Score, and the syntax by which the object returned from the xhr call is tested and passed to the constructor for WinJS.Binding.List
- Lines 235 and 240, in function getTopTenWinLossTie, same as above.
As you can see, the list of needed changes was quite short.
One other change that I needed to make was in the HTML file for the leaderboard in Space Cadet, which uses databinding to display the score on the leaderboard, like so:
1: <div id="myTemplate" data-win-control="WinJS.Binding.Template">
2: <div>
3: <div class="win-type-x-large" style="width: 400px;">
4: <span>Player: </span><em><span data-win-bind="textContent: Player"></span></em>
5: </div>
6: <div class="win-type-large">
7: <span>Score: </span><em><span data-win-bind="textContent: Score"></span></em>
8: </div>
9: </div>
10: </div>
Notice, on line 6, that the textContent property of the second span is being bound to the value “Score”. In the WCF Data Services version, this was “Score1”.
With the leaderboard library for Web API complete, now all I need to do is update the HTML pages for the game client and leaderboard pages to use the new version of the library, like so:
1: <!-- SpaceCadet references -->
2: <link href="/css/default.css" rel="stylesheet" />
3: <link href="/css/space.css" rel="stylesheet" />
4: <script src="/js/leaderboardWebAPI.js"></script>
5: <script src="/js/appBar.js"></script>
6: <script src="/js/default.js"></script>
7: <script src="/js/sound.js"></script>
8: <script src="/js/ship.js"></script>
9: <script src="/js/starField.js"></script>
Now I can run the game (in this case, Space Cadet), and here’s what I’ll see if I beat my previous high score:
which tells me that my service is working properly. But just to be sure, I’ll check the leaderboard as well:
The data (minimal though it may be) reflects what’s in the database, so it’s looking good!
Wrap-up
In this post, you’ve seen how I created a Web Site in Windows Azure, created a new ASP.NET Web API project, used Entity Framework to create a model of the data I showed in the previous version of the leaderboard service, and created a new API Controller, leveraging Visual Studio’s powerful scaffolding to automatically generate an API on top of my data model. I also showed how I could quickly download a Nuget package to add basic OData query support on top of Web API with the addition of a single metadata attribute. You’ve also seen how with a few simple updates, I created a new version of my leaderboard JavaScript library that works with the Web API version of the leaderboard service.
You also saw how I was able to use the new Web Sites feature of Windows Azure to quickly provide a spot in the cloud for my service, with simple Web Deploy-based publishing from Visual Studio to streamline the process of deploying my service to the cloud. While I didn’t have time to cover it in detail in this post, Windows Azure Web Sites also provides robust monitoring, error logging, configuration options, etc. all from the management portal, simplifying the management of my Web Site.
Here are the advantages and disadvantages, in my view, of using ASP.NET Web API for this kind of service:
Advantages
- Mature platform – While Web API is relatively new, SQL Server and Entity Frameworkhave 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, or other formats for which type formatters are configured. Content negotiation makes it pretty easy support multiple types for both input and output. See the ASP.NET Web API site for more information, videos, and tutorials.
- Customizable – ASP.NET Web API is a fully customizable solution, with powerful routing, content negotiation, and other features that you can leverage.
- Query flexibility – While the implementation I showed only supports a subset of query types, OData’s powerful query syntax makes it easy to work with the service through JavaScript, simply by building the appropriate URL and making the appropriate HTTP requests.
Disadvantages
- Complexity – While arguably a little simpler and easier to understand than the WCF Data Services version of the leaderboard service, this version still has a fair number of technologies used. The scenario I’ve shown is deliberately simple, so as to illustrate the end-to-end solution, so expect a little more work when it comes to more complex schemas, adding robust data validation, etc.
- No client library for JavaScript apps – as with the WCF Data Services version, when working with this service from JavaScript, you have to construct your own library for interacting with the service (well, you could just write the code to call the APIs directly in your client code, but who’d do that?).
- New platform – ASP.NET Web API is a newcomer to the ASP.NET stack. While it’s been through a good bit of testing in preview versions, it’s still new enough to unfamiliar to many devs. As I showed, it’s pretty easy to implement, though. And some parts of the stack I used (such as the Nuget package that adds OData support to Web API) are preview releases, which may argue against using them in a production situation.
As with the previous discussion of a WCF Data Services-based leaderboard service, whether ASP.NET Web API is the right solution for your back-end depends on a number of factors, not limited to your familiarity with ASP.NET and related technologies, and your willingness to use relatively new platform technologies. This was my first major foray into Web API, and I was pleasantly surprised by how easy it was to get the basic service up and running. I’m looking forward to digging deeper into Web API, and in fact, it’s one of the technologies that’s under consideration as the basis for the next version of my Community Megaphone site.
What’s Next?
In the next part of this series, I’ll tackle the final approach to building back-end services that I discussed in the overview post, using Windows Azure Mobile Services to create another version of the same leaderboard service.
While you’re waiting, why not sign up for Generation App? There are lots of great resources available for building Windows 8 apps (and now for Windows Phone 8 as well), including new information on a variety of app frameworks from partners that make it fast and easy to build apps and games for Windows 8. It’s free, and you control how often updates are sent, so there’s no good reason to pass it up. Sign up now!
Doesn’t the fact that it’s OData mean there are JavaScript libraries for it? http://www.odata.org/libraries
Apologies, Mike. I should have been clearer. Yes, there are third-party JS libraries for OData. I was referring to official libraries from Microsoft. You could probably use DataJS or JayData, but I haven’t put those through their paces, so didn’t want to complicate things. Thanks for pointing out the libraries!
Funnily enough. I hit a problem today with hosting a WCF OData application. Everything worked fine in the Local hosting scenario. But as soon as I published to the cloud (Azure Extra Small/Small compute) I got errors due to not having enough memory. I’m now re-trying this with the exact same Web API package you mentioned.
Strange. I haven’t seen that…are you loading a lot of entites?
No. It basically had a single table with two rows that it was accessing via entity framework 5 code first. I’m going to try it again this weekend.
Well, let me know if you still have trouble with it. I’d be happy to put you in touch with one of my peers who spends more time in that world.
I deleted everything and started again. This time it seemed to start up ok. Not sure why I was getting the not enough free memory to launch service. I’m beginning to wonder if that was a red herring and there was another issue at the bottom of what was going wrong. I’m glad it’s working as I can now go back to using this instead which makes things easier
Good to hear! Hope it continues working smoothly for you.
Great article/example! For a real-world application is this workable or would you want to create domain entities using EF code-first, then a business layer, then data transfer objects? We are currently working through an architecture like this but once you put in a proper business layer and data transfer objects it seems like you lose a lot of productivity but gain SoC. E.g. the cowboy in me wants to just call EF from controllers, project into anonymous types if necessary, etc. Is there a great model project out there that use EF, business layer, data transfer objects, etc.?
I’m not in the trenches building systems, so take my advice with a grain of salt. But the advantage I see of using ASP.NET Web API is that it can be the basis for just about any client tier. And it’s really up to you how complex you want to make the business layer and/or DTOs.
I’m a fan of the quote from Albert Einstein: “Make everything as simple as possible, but not simpler.” IOW, I tend to look for the simplest solution I can find to a given problem, until it becomes clear that more complexity is needed, rather than the other way ’round.
One thing that you may lose in separating concerns through Web API is the ability to easily propagate validation via metadata attributes, but there are folks who find that somewhat icky to begin with. I’m not a purist, so I think there’s a place for both approaches, depending on what’s most important for a given solution.
Nice work Great Article!!
Thank you. Glad you liked it.
Excellent, I like you article
Thank you. Good to hear.
Absolutely brilliant article, look forward to trying this. Question – not looking for any detail just maybe a couple of keywords I could Google if it’s possible – is there anything built in on the authentication side of things?
Thanks! Glad you liked it. In terms of authentication, you can use many of the techniques you’d use in an ASP.NET MVC app. Basically think of it as securing an HTTP endpoint…can be as simple or complex as your needs dictate.
For an easier authentication route, Windows Azure Mobile Services provides built-in support for authenticating against Twitter, Facebook, Microsoft accounts, and Google accounts, so a lot of the hassles associated with authentication (in particular, getting users to register new credentials for your app) go away.