Programming the Web with AJAX and JSON
So far we have seen how to host services using the WebHttpBinding binding and the WebHttpBehavior endpoint behavior. This allows us to expose services using POX. Many Web developers want to forgo the use of XML and instead use JSON, a simpler format. JSON is well suited for browser applications that need an efficient means of parsing responses from services, and it has the added benefit of integration with JavaScript, the programming language most often used for client-side Web development. JSON is a subset of JavaScript's object literal notation, which means you can easily create objects in JavaScript. Because of this, it's a perfect alternative to using XML for use with AJAX applications.
AJAX stands for Asynchronous JavaScript and XML. AJAX-based Web applications have significant benefits over traditional Web applications. They allow for improved user experience and better bandwidth usage. This is done by improving browser-to-server communication so that the browser does not need to perform a page load. This in turn is done by communicating with a server asynchronously using the JavaScript and the XMLHttpRequest class. Because communication with the server can be done without the need for a page load, developers can create richer user interface experiences approaching that of desktop applications. These types of Web applications are often referred to as Rich Internet Applications, or RIAs.
ASP.NET AJAX Integration
Many frameworks exist for building these AJAX-based Web applications. One of the more popular frameworks is the ASP.NET AJAX framework. This framework has a great client-side and server-side model for building AJAX-enabled Web applications. It includes many capabilities such as a rich client-side class library, rich AJAX-enabled Web controls, and automatic client-side proxy generation for communication with services. It is also based on ASP.NET, which is Microsoft's technology for building Web applications using .NET. WCF already integrates with ASP.NET in .NET Framework 3.0. .NET Framework 3.5 introduces new support for ASP.NET AJAX applications using the WebScriptEnablingBehavior endpoint behavior. This replaces the WebHttpBehavior endpoint behavior. It adds support for using JSON by default and ASP.NET client-side proxy generation. These new capabilities can be used by replacing the webHttp endpoint behavior configuration element with the enableWebScript configuration element.
We created a sample ASP.NET AJAX application called the XBOX 360 Game Review to see how we can use the WebHttpBinding binding and the WebScriptEnablingBehavior to build AJAX-based applications. This simple Web application enables users to provide reviews about their favorite XBOX 360 game. The application was built using an ASP.NET AJAX Web site project template in Visual Studio 2008. Figure 13.2 shows a picture of this Web site.
Figure 13.2 XBOX 360 Game Review AJAX-enabled application
This site has a number of features. First is a list of games that is displayed in a ListBox control to the user. Users can select a game and see a list of comments for each game. Then a user can add comments for the each game. Listing 13.7 lists the service that provides this functionality.
Listing 13.7. GameReviewService.cs
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; namespace EssentialWCF { [ServiceContract(Namespace="EssentialWCF")] [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class GameReviewService { private string[] gamelist = new string[] { "Viva Pinata", "Star Wars Lego", "Spiderman Ultimate", "Gears of War", "Halo 2", "Halo 3" }; private Dictionary<string, List<string>> reviews; public GameReviewService() { reviews = new Dictionary<string, List<string>>(); foreach (string game in gamelist) reviews.Add(game, new List<string>()); } [OperationContract] [WebGet] public string[] Games() { return gamelist; } [OperationContract] [WebGet] public string[] Reviews(string game) { WebOperationContext ctx = WebOperationContext.Current; ctx.OutgoingResponse.Headers.Add("Cache-Control", "no-cache"); if (!reviews.ContainsKey(game)) return null; List<string> listOfReviews = reviews[game]; if (listOfReviews.Count == 0) return new string[] { string.Format("No reviews found for {0}.",game) }; else return listOfReviews.ToArray(); } [OperationContract] [WebInvoke] public void AddReview(string game, string comment) { reviews[game].Add(comment); } [OperationContract] [WebInvoke] public void ClearReviews(string game) { reviews[game].Clear(); } } }
We chose to host this service within Internet Information Server (IIS). Listing 13.8 shows the GameReviewService.svc used to host the service.
Listing 13.8. GameReviewService.svc
<%@ ServiceHost Language="C#" Debug="true" Service="EssentialWCF.GameReviewService" CodeBehind="~/App_Code/GameReviewService.cs" %>
Listing 13.9 shows the configuration information used to host the GameReviewService. The most important aspect of this configuration information is the use of the webHttpBinding binding and the enableWebScript endpoint behavior. This enables the use of JSON and generates the necessary client-side proxy code for the GameReviewService with ASP.NET.
Listing 13.9. web.config
<system.serviceModel> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> <services> <service name="EssentialWCF.GameReviewService" behaviorConfiguration="MetadataBehavior"> <endpoint address="" behaviorConfiguration="AjaxBehavior" binding="webHttpBinding" contract="EssentialWCF.GameReviewService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <endpointBehaviors> <behavior name="AjaxBehavior"> <enableWebScript/> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="MetadataBehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl=""/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
You configure the GameReviewService to be used with ASP.NET by adding a reference to the service using the ASP.NET ScriptManager. Listing 13.10 shows the markup used to reference the GameReviewService. Behind the scenes this is generating client-side script that references a JavaScript file with the client-side proxy. For our example, the URI to the client-side JavaScript is http://localhost/GameReviewService/GameReviewService.svc/js.
Listing 13.10. Referencing Services Using ASP.NET ScriptManager
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="GameReviewService.svc" /> </Services> </asp:ScriptManager>
We have included the ASP.NET Web form used to build the XBOX 360 Game Review Web application. This shows how the services are called from client-side script and how the results are used to dynamically populate controls.
Listing 13.11. Making Client-Side Proxy Calls
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title>XBOX 360 Game Reviews</title> <script type="text/javascript"> function pageLoad() { } </script> </head> <body> <form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="GameReviewService.svc" /> </Services> </asp:ScriptManager> <script type="text/javascript"> EssentialWCF.GameReviewService.set_defaultFailedCallback(OnError); function ListGames() { EssentialWCF.GameReviewService.Games(OnListGamesComplete); } function ListReviews() { var gameListBox = document.getElementById("GameListBox"); EssentialWCF.GameReviewService.Reviews(gameListBox.value, OnListReviewsComplete); } function AddReview() { var gameListBox = document.getElementById("GameListBox"); var reviewTextBox = document.getElementById("ReviewTextBox"); EssentialWCF.GameReviewService.AddReview(gameListBox.value, reviewTextBox.value, OnUpdateReviews); } function ClearReviews() { var gameListBox = document.getElementById("GameListBox"); EssentialWCF.GameReviewService.ClearReviews(gameListBox.value, OnUpdateReviews); } function OnListGamesComplete(result) { var gameListBox = document.getElementById("GameListBox"); ClearAndSetListBoxItems(gameListBox, result); } function OnListReviewsComplete(result) { var reviewListBox = document.getElementById("ReviewListBox"); ClearAndSetListBoxItems(reviewListBox, result); } function OnUpdateReviews(result) { ListReviews(); } function ClearAndSetListBoxItems(listBox, games) { for (var i = listBox.options.length-1; i >-1; i--) { listBox.options[i] = null; } var textValue; var optionItem; for (var j = 0; j < games.length; j++) { textValue = games[j]; optionItem = new Option( textValue, textValue, false, false); listBox.options[listBox.length] = optionItem; } } function OnError(result) { alert("Error: " + result.get_message()); } function OnLoad() { ListGames(); var gameListBox = document.getElementById("GameListBox"); if (gameListBox.attachEvent) { gameListBox.attachEvent("onchange", ListReviews); } else { gameListBox.addEventListener("change", ListReviews, false); } } Sys.Application.add_load(OnLoad); </script> <h1>XBOX 360 Game Review</h1> <table> <tr style="height:250px;vertical-align:top"><td style="width:240px">Select a game:<br /><asp:ListBox ID="GameListBox" runat="server" Width="100%"></asp:ListBox></td> <td style="width:400px">Comments:<br /><asp:ListBox ID="ReviewListBox" runat="server" Width="100%" Height="100%"></asp:ListBox></td></tr> <tr style="vertical-align:top"><td colspan="2"> Enter a comment:<br /> <asp:TextBox ID="ReviewTextBox" runat="server" width="400px"></asp:TextBox> <input id="AddReviewButton" type="button" value="Add" onclick="AddReview()" /> <input id="ClearReviewButton" type="button" value="Clear" onclick="ClearReviews()" /> </td></tr> </table> </div> </form> </body> </html>