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

Accessing Server-Side Data from Client Script: Using Ajax Web Services, Script References, and jQuery

By Scott Mitchell


Introduction


Today's websites commonly exchange information between the browser and the web server using Ajax techniques. In a nutshell, the browser executes JavaScript code typically in response to the page loading or some user action. This JavaScript makes an asynchronous HTTP request to the server. The server processes this request and, perhaps, returns data that the browser can then seamlessly integrate into the web page. Typically, the information exchanged between the browser and server is serialized into JSON, an open, text-based serialization format that is both human-readable and platform independent.

Adding such targeted, lightweight Ajax capabilities to your ASP.NET website requires two steps: first, you must create some mechanism on the server that accepts requests from client-side script and returns a JSON payload in response; second, you need to write JavaScript in your ASP.NET page to make an HTTP request to this service you created and to work with the returned results. This article series examines a variety of techniques for implementing such scenarios. In Part 1 we used an ASP.NET page and the JavaScriptSerializer class to create a server-side service. This service was called from the browser using the free, open-source jQuery JavaScript library.

This article continues our examination of techniques for implementing lightweight Ajax scenarios in an ASP.NET website. Specifically, it examines how to create ASP.NET Ajax Web Services on the server-side and how to use both the ASP.NET Ajax Library and jQuery to consume them from the client-side. Read on to learn more!

An Quick Overview of ASP.NET Ajax Web Services


The ASP.NET Ajax Library is a suite of Web controls and client-side script that simplifies implementing common Ajax scenarios in ASP.NET websites. The ASP.NET Ajax Library initially was released as an add-on to ASP.NET version 2.0, but was included as part of the .NET Framework starting with version 3.5. One of the most common ASP.NET Ajax Library Web controls is the UpdatePanel, which allows for the page developer to define a region in an ASP.NET web page for which Ajax techniques should replace the standard postback model. The UpdatePanel allows developers to implement Ajax functionality without having to write a single line of JavaScript. Such power, however, comes at a cost - the UpdatePanel, while simple to use, transmits a large amount of data between the browser and server when making an asynchronous call. In order to design more streamlined and responsive applications using the ASP.NET Ajax Library, developers must ditch the UpdatePanel and use Ajax Web Services instead.

Web Services are a mechanism by which two computers can interact over a network. Web Services are typically implemented using the client/server model, where a server exposes a Web Service which is then consumed by a client. For example, a weather website might provider a web service that returns current conditions for a particular city. A client application, be it a desktop application or another website, could call this web service passing along the city of interest. The weather website would respond by returning the current conditions for that city, which could then be displayed by the client application. In a nutshell, Web Services allow for machine-to-machine communication by using standardized transport and encoding protocols. Typically, web services serialize the data exchanged between them using SOAP, which is an XML-based protocol for encoding messages.

Creating and consuming Web Services in ASP.NET is easy because the .NET Framework handles all of the low-level, nitty-gritty work, such as generating, sending, receiving, and parsing the SOAP messages. To create a Web Service you just need to add a new Web Service file to your website project. This creates a class where you can define what methods are accessible via the Web Service. Likewise, when consuming a Web Service from a desktop or web application, you just point to the Web Service and Visual Studio automatically creates a proxy class, which has the same interface as the Web Service. To call the Web Service from the client application, simply call the corresponding method in the proxy class.

While it is certainly possible to invoke a Web Service from a browser using JavaScript, it is not trivial because the script calling the Web Service must create a properly formatted SOAP message as its request and then parse the SOAP message returned by the Web Service. While creating and parsing XML in JavaScript is not rocket science, it's not trivial, either. Ideally, when calling a Web Service from JavaScript the information included in the request and response would be serialized using JSON. The good news is that the ASP.NET Ajax Library makes it a cinch to create Web Services that can work with JSON. As we'll see shortly, all that we need to do is create a Web Service as we normally would and then add a ScriptService attribute to the Web Service class.

For a deeper look at ASP.NET Ajax Web Services, refer to my article Building Interactive User Interfaces with Microsoft ASP.NET AJAX: Retrieving Server-Side Data Using Web Services. For more background on creating and consuming Web Services, see An Extensive Examination of Web Services.

Creating an ASP.NET Ajax Web Service


Part 1 showed how to access server-side data from client-side script through a simple master/details report web page. Specifically, the web page contained a drop-down that listed the categories in the Northwind database. Upon selecting a category, details about those products in that category were listed. This information - the list of categories and those products in the selected category - were returned by the server-side service, which, in Part 1, was implemented using an ASP.NET page. Let's continue this demo here in this article, but use an ASP.NET Ajax Web Service instead of an ASP.NET web page. (The Web Service we'll create, as well as the web pages for accessing its data, are included in the download available for download at the end of this article.)

Start by adding a new Web Service to your ASP.NET website by right-clicking on the folder where you want the service's endpoint to exist, choosing Add New Item, and then picking the Web Service template. Name this new Web Service NorthwindService.asmx.

Add a Web Service named NorthwindService.asmx to your website.

The code for the Web Service is located in a separate file named NorthwindServices.asmx.vb or NorthwindServices.asmx.cs and is where we define the methods for the Web Service. At this point it should contain a constructor and a dummy method named HelloWorld. Delete these two methods. At this point you should have an empty Web Service class file that looks similar to the following:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class NorthwindService : System.Web.Services.WebService {

}

We need two methods for this WebService: GetCategories, which returns information about the categories; and GetProductsByCategory(categoryId), which returns product information for the specified category. But what data type should these methods return, precisely? In Part 1 we created an object model for our application using Linq-To-Sql. This generated classes named Category and Products that model data from the Northwind database's Categories and Products table, respectively. Consequently, we might consider returning an array of Category objects. However, this won't work because the Web Service cannot serialize the Category object into XML or JSON. The Category class has a Products property that returns the set of products associated with that category; each Product object has a Category property that references the product's category. When .NET attempts to serialize a Category (or Product) object, then, it gets stuck in this loop and cannot perform the serialization. Even if we could serialize the Category object we still might not want to use it as a return type for our Web Service's GetCategories method. The Category class may include properties we don't want to send back to the client (such as Picture).

It's not uncommon for developers to create new objects whose sole purpose is to exchange data between a client and the server-side service. These objects are referred to as Data Transfer Objects, or DTOs, and contain just the set of properties that need to be communicated between the client and server. Typically, the do not have any methods or events - just properties.

The demo application includes two DTO classes - CategoryDTO and ProductsDTO - which are used to ferry information between the browser and server. The GetCategories method, then, returns an array of CategoryDTO objects.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class NorthwindService : System.Web.Services.WebService {
   [WebMethod]
   public CategoryDTO[] GetCategories()
   {
      using (var dbContext = new NorthwindDataContext())
      {
         var results = from category in dbContext.Categories
                       orderby category.CategoryName
                       select new CategoryDTO()
                       {
                          CategoryID = category.CategoryID,
                          CategoryName = category.CategoryName,
                          Description = category.Description
                       };

         return results.ToArray();
      }
   }

   ...
}

Note: If you are using ASP.NET 4, which supports dynamic typing, you can opt to forgo using DTOs and instead have your Web Service methods return anonymous types. For more information on this approach, see Returning Dynamic Types from an Ajax Web Service Using C# 4.0.

Notice that the GetCategories method is marked with the WebMethod attribute. This tells .NET that this method can be called from a client invoking the service. This method returns an array of CategoryDTO objects, which are retrieved using a LINQ query. Specifically, for each Category object in dbContext.Categories a new CategoryDTO object is created with its CategoryID, CategoryName, and Description properties assigned to the CategoryID, CategoryName, and Description properties of the returned Category object. The LINQ query is executed and its results returned as an array of CategoryDTO objects by calling results.ToArray().

The GetProductsByCategory method is quite similar to GetCategories, the only substantial difference being that it accepts an integer input parameter named categoryId and only returns those products that belong to that category.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class NorthwindService : System.Web.Services.WebService {
   ...

   [WebMethod]
   public ProductDTO[] GetProductsByCategory(int categoryId)
   {
      using (var dbContext = new NorthwindDataContext())
      {
         var products = from product in dbContext.Products
                        where product.CategoryID == categoryId
                        orderby product.ProductName
                        select new ProductDTO()
                        {
                           ProductName = product.ProductName,
                           QuantityPerUnit = product.QuantityPerUnit,
                           UnitPrice = product.UnitPrice,
                           Discontinued = product.Discontinued
                        };

         return products.ToArray();
      }
   }

}

At this point we have a working Web Service! However, the Web Service is not currently configured to support JSON-formatted requests and responses. The good news is that enabling such functionality is easy - we just need to add the ScriptService attribute to the Web Service class. In fact, the attribute definition is already there - just uncomment it and you're all set!

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class NorthwindService : System.Web.Services.WebService {
   ...
}

At this point we have the server-side service completed. We're now ready to investigate how to call this Ajax Web Service from client-side script. There are two ways by which you can invoke an Ajax Web Service from client-side script:

  • Have the ASP.NET Ajax Library create a client-side proxy class for you and use that, or
  • Call the Ajax Web Service directly from script using jQuery
We will explore both of these options, starting with using a proxy class.

Calling an Ajax Web Service Using a Proxy Class


As noted earlier in this article, a Web Service is typically consumed by a client through the use of a proxy class. A proxy class's purpose is to encapsulate the complexity involved in calling the Web Service. Invoking a Web Service involves several steps, including: serializing any information that must be sent to the Web Service into the appropriate format; calling the Web Service; and parsing the data returned from the Web Service. The proxy class simplifies calling a Web Service by handling these low-level steps. In short, the proxy class has the same methods as the Web Service. When the client calls one of these methods, the proxy class does all the nitty-gritty work of serializing the input parameters, calling the web service, and deserializing the response.

The ASP.NET Ajax Library will create a JavaScript proxy class for us that we can then use to invoke the server-side Web Service. To create this proxy class add a ScriptManager control to the ASP.NET page that contains the script that makes the Web Service call. Next, add a <Services> section to the proxy class that specifies the URL of the Web Service like so:

<asp:ScriptManager ID="ScriptManager1" runat="server">
   <Services>
      <asp:ServiceReference Path="pathToWebService" />
   </Services>
</asp:ScriptManager>

Note: If you have a ScriptManager already defined in your master page you can either add the script references to that ScriptManager or, better yet, you can add a ScriptManagerProxy control to your ASP.NET page and define the services there.

This addition causes the ScriptManager to automatically generate a JavaScript proxy class and make it accessible to the page. To call the Web Service from JavaScript you simply enter the fully qualified type of the Web Service (such as NorthwindServices or, if you have placed the Web Service in a namespace, then Namespace.NorthwindServices), passing in any parameters. The proxy class calls the service asynchronously, meaning that you need to provide a JavaScript function that will be called once the Web Service call completes. It is from this function that you'll update the page's display to incorporate the information returned by the Web Service. (You can also specify a function to call if the Web Service call resulted in an error.)

The JavaScript for populating the drop-down list with the set of categories follows. We start by calling the NorthwindService.GetCategories method, specifying the function to execute when the response returns as the first parameter of this method. This "on success" function is passed the data returned by the Web Service, which we then enumerate using jQuery's $.each function, adding a new <option> element to the drop-down for each category returned by the Web Service.

// Populate the categories DDL
NorthwindService.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");
      });
   }
);

When calling the GetProductsByCategory we pass in two input parameters: the categoryId value the user has selected from the drop-down and the function to execute when the results return:

// Get the category products and display them!
NorthwindService.GetProductsByCategory(categoryId, function (data) { ... });

The download available at the end of this article includes the complete script used to power this master/details report page. After downloading the demo and trying it out, take a moment to compare the script used in this demo with the script used in Part 1. The only differences are how the data's retrieved from the server-side service. In Part 1, we used jQuery's getJSON function. Here, we use a proxy class created for us by the ASP.NET Ajax Library. Other than that, the rest of the code is identical. The same code is used to enumerate the results returned by the service and display them on the page.

Calling an Ajax Web Service Using jQuery


The proxy class created by the ASP.NET Ajax Library makes calling the Ajax Web Service a walk in the park, but has some hidden (albeit minor) performance costs. The proxy class is dynamically created on the web server, so the browser must make an extra request to the server to load this proxy class, which is usually in the neighborhood of 100 KB in size. In addition to this proxy class, the ScriptManager control also includes additional JavaScript references that the browser must download from the server. (Granted, most of these costs are a one time hit and will not need to be remade when the user visits this page later as the browser will cache the proxy class and other JavaScript resources.)

It is possible to bypass referencing the proxy class and instead to call the Ajax Web Service directly from JavaScript using jQuery. However, doing so requires a little more work on your end (although not much more). The Web Service can be accessed directly by making an HTTP request to your Web Service endpoint (NorthwindService.asmx) and specifying the method name like so:

NorthwindService.asmx/methodName

What's more, you need to make a POST request to the Web Service and set the Content-Type of the request to application/json, which is the MIME type for JSON data. If you do not perform these three requirements the Web Service will throw an exception and you won't get back the data you expect.

To make such a nuanced request to the Web Service we need to use jQuery's ajax function, which takes a variety of parameters as input and makes an HTTP request to a specified URL. The getJSON function will not suffice here because it does not let you specify the verb or Content-Type like ajax does.

The following jQuery call uses the ajax function to invoke the NorthwindServer.asmx Web Service's GetCategories method:

$.ajax({
   type: 'POST',
   url: 'Services/NorthwindService.asmx/GetCategories',
   contentType: 'application/json',
   dataType: 'json',
   success: function (data) {
               ...
            }
});

Let's look at each parameter passed into the ajax function.

  • type specifies the HTTP request's verb; it defaults to GET, but we need to set it to POST in order for the Ajax Web Service to work as expected. While it is possible to configure an Ajax Web Service to accept GET requests, POSTs are recommended due to the security concerns noted in Scott Guthrie's blog entry, JSON Hijacking and How ASP.NET AJAX 1.0 Avoids these Attacks.
  • url specifies the URL the request is made to.
  • contentType specifies the Content-Type header of the request; for Ajax Web Services this must be set to application/json.
  • dataType specifies the format the data will be returned in.
  • success specifies the function to execute when the response returns from the server This function is passed the parsed JSON results as its first input parameter.
Note: If you are using jQuery version 1.4.2 or earlier, you should add the attribute data: '{}' whenever making a POST request via the ajax function that otherwise would have an empty POST body. Earlier versions of Firefox do not send a Content-Length header when making empty POST requests, but certain web servers, such as IIS 6 and IIS 7, require that the Content-Length be specified regardless. Using a non-empty data value prompts Firefox to send the content length information in the request headers. jQuery version 1.4.3 (which this demo uses) includes a fix that forces Firefox to send a Content-Length value even for empty POST requests. See this discussion and jQuery Ticket #4044 for more details. (Special thanks to Dave Ward for pointing out this bug to me and helping me noodle through it.)

In Part 1 we created an ASP.NET page for our server-side service that returned category and product information as JSON. For instance, when calling that service and requesting the category information we got back 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!!"
   },
   ...
]

The Ajax Web Service returns slightly different JSON. Rather than serializing the array of CategoryDTOs directly into JSON, the Ajax Web Service instead creates a wrapper class with a single property named d that is assigned to this array of CategoryDTOs. It is this wrapper class that it then serialized into JSON. (Also, each object in the array includes a __type field that specifies the returned object's type.) The (formatted and abbreviated) JSON payload returned when calling the Web Service's GetCategories method follow:

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

Consequently, the object returned to the "on success" function is the wrapper object. To get to the "real" data you need to access the d property. Here's the "on success" function when calling the GetCategories method. Note that the $.each function enumerates through the elements of data.d.

$.ajax({
   ...
   success: function (data) {
            $.each(data.d, function (index, elem) {
                  $("<option />")
                        .text(elem.CategoryName)
                        .val(elem.CategoryID)
                        .appendTo("#categories");
            });
         }
});

There's also a bit more work involved when calling an Ajax Web Service method that expects one or more input parameters. Specifically, these input parameters need to be serialized into JSON. To turn a JavaScript object into its corresponding JSON string, download Douglas Crockford's json2.js script. Add it to your project and add a <script> reference to it in your master page. Once in place, you can turn any JavaScript object into its JSON representation using JSON.stringify(object).

Here is the ajax function call to the Web Service's GetProductsByCategory method. Recall that this method has a single input parameter - an integer named categoryId. To supply this input parameter we must create a JavaScript object that has a single property named categoryId with the value we want to pass into the Web Service method. This object then needs to be converted into a JSON string and passed as the data of the POST request. This is handled by generating the string using JSON.stringify({ "categoryId": categoryId }) and then assigning that string in the data parameter of the ajax function call.

// Get the category products and display them!
$.ajax({
   type: 'POST',
   url: 'Services/NorthwindService.asmx/GetProductsByCategory',
   contentType: 'application/json',
   dataType: 'json',
   data: JSON.stringify({ "categoryId": categoryId }),
   success: function (data) { ... }
});

Calling an Ajax Web Service without using the ASP.NET Ajax Library-created proxy class is certainly possible, but requires that we jump through a few extra hoops. Whether jumping through these extra hoops are worth it to have a more responsive application and to standardize on a single JavaScript framework (i.e., using just jQuery and not both jQuery and the ASP.NET Ajax Library) is a decision you will have to make for yourself.

Happy Programming!

  • By Scott Mitchell

  • Read Part 3: Using WCF Services with jQuery and the ASP.NET Ajax Library


    Attachments:


  • Download the Demo Code Used in this Article

    Further Reading


  • Accessing Server-Side Data from Client Script: Accessing JSON Data From an ASP.NET Page Using 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: Retrieving Server-Side Data Using Web Services
  • Building Interactive User Interfaces with Microsoft ASP.NET AJAX: A Look at JSON Serialization
  • Returning Dynamic Types from an Ajax Web Service Using C# 4.0
  • Client-Side Web Service Calls with AJAX Extensions
  • Understanding ASP.NET AJAX Web Services
  • Using jQuery to Consume ASP.NET JSON Web Services
  • Article Information
    Article Title: Accessing Server-Side Data from Client Script: Using Ajax Web Services, Script References, and jQuery
    Article Author: Scott Mitchell
    Published Date: October 27, 2010
    Article URL: http://www.4GuysFromRolla.com/articles/102710-1.aspx


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