To read the article online, visit http://www.4GuysFromRolla.com/articles/102010-1.aspx

Accessing Server-Side Data from Client Script: Accessing JSON Data From an ASP.NET Page Using jQuery

By Scott Mitchell


Introduction


When building a web application, we must decide how and when the browser will communicate with the web server. The ASP.NET WebForms model greatly simplifies web development by providing a straightforward mechanism for exchanging data between the browser and the server. With WebForms, each ASP.NET page's rendered output includes a <form> element that performs a postback to the same page whenever a Button control within the form is clicked, or whenever the user modifies a control whose AutoPostBack property is set to True. On postback, the server sends the entire contents of the web page back to the browser, which then displays this new content. With WebForms we don't need to spend much time or effort thinking about how or when the browser will communicate with the server or how that returned information will be processed by the browser. It just works.

While this approach certainly works and has its advantages, it's not without its drawbacks. The primary concern with postback forms is that they require a large amount of information to be exchanged between the browser and the server. Specifically, the browser sends back all of its form fields (including hidden ones, like view state, which may be quite large) and then the server sends back the entire contents of the web page. Granted, there are scenarios where this large quantity of data needs to be exchanged, but in many cases we can use techniques that exchange much less information. However, these techniques necessitate spending more time and effort thinking about how and when to have the browser communicate with the server and intelligently deciding on what information needs to be exchanged.

This article, the first in a multi-part series, examines different techniques for accessing server-side data from a browser using client-side script. Throughout this series we will explore alternative ways to expose data on the server so that it can be accessed from the browser using script; we will also examine various tools for communicating with the server from JavaScript, including jQuery and the ASP.NET AJAX library. Read on to learn more!

An Example: A Simple Master/Child Report Web Page


Consider a simple master/detail report web page like the one shown below, which uses a drop-down to list the categories of products for sale. Selecting a category from the drop-down displays information about those products that belong to the selected category.

A simple master/detail report lists the products for the selected category (Beverages).

Traditionally, this master/detail report web page would be implemented using a DropDownList control for the list of categories and a ListView control (or GridView) for the list of products, with the DropDownList control's AutoPostBack property set to True. By setting the AutoPostBack property to True, the DropDownList control injects a bit of JavaScript into the page so that whenever its selection is changed the form is submitted. The form's submission prompts the browser to re-request the web page, sending along all of the form fields (including the just-selected category). On postback we would repopulate the products ListView with those products that belong to the selected category and send back the entire page's HTML, which the browser would then display.

While this approach certainly works and is quick and easy to implement, it has the disadvantage of being very "chatty." Every time there's a new category selected the form is submitted, resulting in a new HTTP request to the server, which sends all of the form fields (including view state). Moreover, the server doesn't just return the "new" markup (namely, those products for the selected category), but needlessly returns all of the page's markup, including the DropDownList markup and other content on the page that has not changed.

The demo available for download at the end of this article includes a page named TraditionalApproach.aspx that implements the above master/details report page using the techniques we just discussed. Let's examine how much information in exchanged between the client and the server with this approach. First off, whenever the user chooses a new category the browser submits the form, which results in a payload of 2,398 bytes. In a perfect world, we'd need to only send a fraction of this information to the server. After all, we only need to report the newly selected category, right? Now what about the data that gets sent back from the server? The response from the web server for this demo clocks in at 10,424 bytes. However, the actual markup that is specific to the list of products in the selected category comes in at less than 4,500 bytes, meaning that more than half of the data sent from the server down to the browser was markup that did not change, things like the layout markup, the markup for the categories drop-down, and so on.

The goal of this article series is to explore alternative, more streamlined ways for reducing the amount of data exchanged between the browser and the server. As we'll see, with a bit of JavaScript and a touch of work on the server, it is possible to have the browser send only the necessary data to the server and to have the server return just the pertinent information. Implementing these techniques involves completing the following tasks:

  1. Building a server-side service that returns or modifies data when invoked. In the master/detail report example, we'd need a service that returns the list of categories (for the drop-down list) as well as details about the products in a specific category.
  2. Writing JavaScript code that invokes the service (when needed) and displays the results. For our example, we'd need to invoke the service to get the list of categories whenever the page is first visited. Also, whenever the drop-down list is changed we'd want to call the service to get the list of products for that category. In both cases, we need to take the data returned from the service and display it in the web page.
Before we examine these two tasks, let's first talk about how the browser and web server will serialize the data they need to exchange.

Exchanging Information Between the Browser and Server


One of the nice things about using the traditional WebForms model is that we didn't have to give a second thought as to how data would be exchanged between the browser and the server, as the browser and server already know how to exchange information during a form submission. When exchanging data using JavaScript-based techniques, we must give a little more forethought to this question. Namely, we must answer, How will the browser send the pertinent information to the web server (such as the category for which to view products) and how will the server return the response?

How the browser sends its data to the server usually depends on whether the browser is asking to retrieve data or if it is modifying data. Each HTTP request specifies a method, which indicates the type of action to be performed. The HTTP protocol supports a variety of methods, the two most common being GET and POST. A GET request indicates that the browser wishes to retrieve a resource and that no other action should be performed, whereas a POST request is used when submitting data to be processed. (An ASP.NET WebForm is referred to as a postback form because when submitted, the browser makes a POST request back to the same web page, sending the form field names and values as the payload of the POST request.) So, when retrieving data from the server the browser should use a GET request and when modifying data a POST request should be used. The only way a browser can send information to the server through a GET request is via the URL (such as in the querystring) or as an HTTP Header. Therefore, when using script to retrieve data from the server any information that needs to be supplied in order to retrieve that data should be specified in the querystring. (For this article, I want to focus on the data retrieval scenario; we'll look at data modification examples in a future installment.)

The server usually sends back a much larger payload to the browser. In our example, the browser only needs to specify which category the user selected, whereas the server needs to return information about those products that belong to the specified category. But how does the server return the product information in a way that the browser can understand? One option is to serialize the product information into XML. Another option, which is usually preferred when the data is being returned to a browser, is to use JavaScript Object Notation, or JSON. JSON is an open, text-based serialization format, that is both human-readable and platform independent. It differs from XML in three important ways: it is much simpler to understand and implement than XML; it is less verbose, resulting in a slimmer payload; and, most importantly, data serialized in JSON can intuitively be parsed by JavaScript. For more information on JSON, refer to Building Interactive User Interfaces with Microsoft ASP.NET AJAX: A Look at JSON Serialization and An Introduction to JavaScript Object Notation (JSON) in JavaScript and .NET.

Creating a Server-Side Service For Returning Data


In order to implement the master/detail report example, we need a service that returns the CategoryID, CategoryName, and Description fields of all of the categories in the database as well as the ProductName, QuantityPerUnit, UnitPrice, and Discontinued fields of those products that belong to a specified category. Such a service should be accessible over HTTP by requesting a URL like http://www.yoursite.com/Services/GetCategories.svc or http://www.yoursite.com/Services/GetProductsInCategory.aspx?CategoryID=1 and should return the requested data as JSON.

There are a variety of ways in which such a service could be constructed. This article examines using an ASP.NET page as the service. (Future installments will explore additional options.) Let's start by using an ASP.NET page. The demo available for download includes a folder named Services, inside which you'll find a page named NorthwindService.aspx. This is an ASP.NET page that contains no declarative markup (I removed all content from the .aspx page except for the @Page directive). My idea here was to have this service be invokable in the general format NorthwindService.aspx/ACTION/ID, where ACTION is a required string indicating the type of request to perform and ID is optional, depending on the ACTION. Specifically, the service is built to handle the following cases:

  • NorthwindService.aspx/GetCategories - returns the CategoryID, CategoryName, and Description values of all of the categories in the database, and
  • NorthwindService.aspx/GetProductsByCategory/CategoryID - returns the ProductName, QuantityPerUnit, UnitPrice, and Discontinued fields of those products whose CategoryID field equals the value specified by CategoryID.
The actual work is performed in the code-behind's Page_Load event handler. First, the page's Content-Type is set to application/json, which is the MIME type for JSON data.

protected void Page_Load(object sender, EventArgs e)
{
   Response.ContentType = "application/json";

   ...

Next, the Request.PathInfo value is split into its individual parts. Request.PathInfo is a little known property that returns the path information for a resource with a URL extension. A URL extension is that part that comes after the name of the web page, namely the /GetCategories or /GetProductsByCategory/CategoryID. Here we split on the forward slash character to see if the user wants to retrieve categories or products. (Note: Error checking code has been removed for brevity.)

   ...

   string output = string.Empty;

   var parts = Request.PathInfo.Split("/".ToCharArray());

   if (string.CompareOrdinal(parts[1], "GetCategories") == 0)
   {
      ... Get category information ...
   }
   else if (string.CompareOrdinal(parts[1], "GetProductsByCategory") == 0)
   {
      int categoryId = Convert.ToInt32(parts[2]);

      ... Get product information for specified category ...
   }

   Response.Write(output);
}

In the above code I have a string variable named output, which is what is sent back to the browser at the end of the Page_Load event handler (via a Response.Write). Depending on whether the user requested to retrieve all categories or the products in a specific category, the data will be retrieved, serialized into JSON, and stored in the output variable.

To understand how this works, let's see the code that executes when the user specifies a GetCategories action.

   ...

   if (string.CompareOrdinal(parts[1], "GetCategories") == 0)
   {
      using (var dbContext = new NorthwindDataContext())
      {
         var categories = from category in dbContext.Categories
                          orderby category.CategoryName
                          select new
                          {
                             category.CategoryID,
                             category.CategoryName,
                             category.Description
                          };

         output = categories.ToJson();
      }
   }
   else if (string.CompareOrdinal(parts[1], "GetProductsByCategory") == 0)
   {
      ... Get product information for specified category ...
   }

Note that in the above code I use a LINQ query to retrieve the CategoryID, CategoryName, and Description fields form all of the categories in the database. Next, this collection of data is converted into JSON by calling the ToJson method. This method does not exist in the .NET Framework; rather, it's an extension method I have created in another class in the project (JsonExtensions.cs). This extension method uses the .NET Framework's JavaScriptSerializer class to serialize a .NET object (in this case, a collection of category objects) into JSON. The code to serialize data into JSON using the JavaScriptSerializer class is quite simple:

var serializer = new JavaScriptSerializer();
string json = serializer.Serialize(objectToSerialize);

Testing the Service


With this code in place, you can test the service by visiting it directly through a browser. For instance, visiting NorthwindService.aspx/GetCategories returns the following (formatted and abbreviated) JSON:

[
   {
      "CategoryID":1,
      "CategoryName":"Beverages",
      "Description":"Soft drinks, coffees, teas, beers, and ales"
   },
   {
      "CategoryID":30,
      "CategoryName":"Biscuits",
      "Description":"Tasty, tasty biscuits!!"
   },
   ...
]

In JSON, the square brackets denote an array while the curly braces denote an object. Therefore, the above JSON represents an array of objects, where each object has three properties: CategoryID, CategoryName, and Description. To see the JSON returned by the GetProductsByCategory action, visit NorthwindService.aspx/GetProductsByCategory/1, which returns those products that belong to the Beverages category.

Calling the NorthwindService.aspx Service from Client-Side Script


Now that we have the server-side service complete, we're ready to call it from a web page using client-side script! While you can write the native JavaScript to invoke the service and process its results, I'd strongly recommend using a JavaScript library like jQuery. jQuery is a lightweight, cross-browser JavaScript library designed to ease JavaScript's most common tasks, including inspecting and manipulating the Document Object Model (DOM) and making out of band HTTP requests to support AJAX functionality. In plain English, jQuery makes it easy to perform client-side tasks like adding or removing attributes or CSS classes to elements in the DOM, or showing or hiding elements on the page in response to a user action (such as clicking a button). What's more, jQuery ships with Visual Studio 2010 and is automatically included in new ASP.NET Web Sites and ASP.NET Web Applications.

The demo application includes an ASP.NET page named UsingAnASPNETPage.aspx that shows how to call the ASP.NET server-side service. This web page's markup includes a drop-down list with just one item (-- Select a Category --) and a <div> element where the selected category's products will be displayed.

<p>
   <b>Choose a Category:</b>
   <select id="categories">
      <option value="-1">-- Select a Category --</option>
   </select>
</p>
<div id="products"></div>

The categories drop-down list and products <div> are populated by client-side script that calls the server-side service. When the page loads we start by invoking the service's GetCategories action. To make an HTTP GET request to a URL that returns JSON data, use jQuery's getJSON function, which takes two input parameters: the URL to request and the function to execute once the data is returned. When the data is returned we want to enumerate the results and for each category add a new <option> element to the drop-down list. This is accomplished with the following JavaScript:

// Populate the categories DDL
$.getJSON('Services/NorthwindService.aspx/GetCategories',
         function (data) {
            $.each(data, function (index, elem) {
               // Create a new <option>, set its text and value, and append it to the <select>
               $("<option />")
                  .text(elem.CategoryName)
                  .val(elem.CategoryID)
                  .appendTo("#categories");
            });
         }
);

The above code invokes the NorthwindService.aspx/GetCategories service. When the data is returned, jQuery's each function is used to iterate over the results. For each category returned, a new <option> element is created, its inner text is set to the value of the category's CategoryName property; its value attribute is set to the CategoryID. Following that, the <option> element is appended to the categories drop-down list.

Recall that the service returns an array of category objects (as JSON). When the call to the service completes and this information is returned to the browser, the function defined as the second parameter in getJSON is executed. Here, data is the array of categories. The each function steps through each category. Here, elem is the instance of the category object we are currently iterating through. Note that we can access the properties of this object using elem.PropertyName.

After the above code executes, the drop-down lists the categories in the database. We also need to call the service whenever the user selects a new category. To accomplish this, we create a client-side event handler for the drop-down's change event. First off, we need to determine the selected CategoryID, which can be accessed via $("#categories").val(). If the selected CategoryID equals -1 then that means the user chose the "-- Select a Category --" option, so we want to hide the products <div>; otherwise, we need to call the service, passing in the selected CategoryID. This is done using the getJSON function. When the server returns its data, we need to iterate through the data and build up the HTML to display in the products <div>.

// Determine the CategoryID of the selected category
var categoryId = $("#categories").val();
         
// If the --Select Category-- option is selected, hide the product display
if (categoryId == -1)
   $("#products").hide();
else {
   // Get the category products and display them!
   $.getJSON('Services/NorthwindService.aspx/GetProductsByCategory/' + categoryId,
         function (data) {
            var output = '';

            if (data.length == 0)
               output = '<i>There are no products in this category...</i>';

            $.each(data, function (index, elem) {
               // Build up the string of HTML to display product information
               output += "<div class=\"product\">" +
                         "   <h3>" + elem.ProductName + "</h3>" +
                         "   <div class=\"details\">" +
                         "      <b>Qty/Unit:</b> " + elem.QuantityPerUnit + "<br />" +
                         "      <b>Price:</b> $" + elem.UnitPrice.toFixed(2) + "<br />" +
                         "      <b>Discontinued:</b> " + elem.Discontinued +
                         "   </div>" +
                         "</div>";
            });

            $("#products").show().html(output);
         }
   );
}

As we saw before, the getJSON function takes two input parameters - the URL of the service and a function that is executed when the server responds with its JSON payload (namely, an array of product objects). In the function that executes when the server responds, we start by checking to see if the array of products returned from the server contains no products. In that case, we display, "There are no products in this category..." Otherwise, we loop through the set of products (using the each function, as before) and build up a string of HTML to display. At the conclusion of the loop, we display the HTML in the products <div> using jQuery's html function.

Evaluating Our Progress


With this code in place, let's walk through what happens when a user visits our page.
  1. The user points their browser to UsingAnASPNETPage.aspx. The server responds with the markup for this page.
  2. The markup for this page includes JavaScript that immediately makes a request back to the server (NorthwindService.aspx/GetCategories). The server responds with a JSON payload listing the categories in the database. Upon receipt, the browser enumerates the categories and adds an <option> element to the drop-down list for each returned category.
  3. If the user selects a category from the drop-down, the browser makes a request for the service NorthwindService.aspx/GetProductsByCategory/CategoryID. The server responds with a JSON payload listing the products in the specified category. Upon receipt, the browser enumerates the products and builds up an HTML string displaying the pertinent information. This string is then displayed within the products <div>.
So, how does this page compare to the traditional approach? Admittedly, using a client-side centric approach to retrieving data from the server involved several more steps than using a traditional WebForms-based approach. Also, if you are fairly new to jQuery and JavaScript then it may have taken quite a bit longer time-wise to build this page than it would have using a traditional approach. The benefits of this approach can be seen by examining the payload differences. Recall that with the traditional approach there was, roughly, 2,400 bytes sent from the client to the server and 10,500 bytes sent from the server to the client whenever the user selected a new category. With our streamlined approach, this has plummeted to 578 bytes on the request (which is virtually all from unrelated HTTP Headers) and a scant 1,582 bytes on response. In other words, we reduced the total request/response payload size by nearly 85%!

Looking Forward...


This article explored one way to build a server-side service and one way to access the data from script; namely, we focused on using an ASP.NET page to serve data and jQuery to retrieve it. Moreover, we only examined how to retrieve data from the server. In future installments we'll explore alternative techniques for implementing server-side services and for accessing them from the client, and we'll see how to use these techniques to perform inserts, updates, and deletes, as well.

Until then, Happy Programming!

  • Read Part 2: Using Ajax Web Services, Script References, and jQuery!

  • By Scott Mitchell


    Attachments:


  • Download the Demo Code Used in this Article

    Further Reading


  • Accessing Server-Side Data from Client Script: Using Ajax Web Services, Script References, and jQuery
  • Accessing Server-Side Data from Client Script: Using WCF Services with jQuery and the ASP.NET Ajax Library
  • Building Interactive User Interfaces with Microsoft ASP.NET AJAX: A Look at JSON Serialization
  • An Introduction to JavaScript Object Notation (JSON) in JavaScript and .NET
  • Article Information
    Article Title: Accessing Server-Side Data from Client Script: Accessing JSON Data From an ASP.NET Page Using jQuery
    Article Author: Scott Mitchell
    Published Date: October 20, 2010
    Article URL: http://www.4GuysFromRolla.com/articles/102010-1.aspx


    Copyright 2014 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers