Passing Data

Passing Data

The X in AJAX is for XML, and indeed many web services today use XML for sending and receiving data. XML parsers are readily available in the browser, and classes for creating, manipulating, and serial-izing XML data on the server are plentiful. XML has also been the foundation for the creation of applications that leverage service-oriented architectures.

As developers have recognized the potential for passing data between client and server using XML, they have also recognized that XML is not always the ideal format for working with data. This section discusses JavaScript Object Notation (JSON), which is a JavaScript-specific method of serializing data that has less overhead than XML.

Serialization

Serialization is the process of encoding data in a way that allows it to be sent over a serial connection, such as an HTTP channel, that will allow it to be understood on the other side of the connection and can then be deserialized back into its original form. This is commonly used with structured types, since simple types such as strings are inherently compatible with serial channels. When objects are held in memory by a single application, there isn’t a need to serialize them. It doesn’t really matter how the computer represents them in memory as long as the fields, properties, and methods behave as expected. But when objects need to cross process or machine boundaries, serialization becomes essential in order for that object to be faithfully reconstructed at the other end.

The format used to serialize an object must be agreed upon by both parties that are exchanging it. If the format is not understood by both sides, the object will be corrupted after being transferred since the deserialization routine won’t know how to re-create the object. For example, if I write a song, record it, and give you the file as an MP3, chances are you will have access to a device or program that can play it. My MP3 file is a serialization of the song. I could decide that I would record each instrument separately and encode it that way in the file so that I could easily modify a single instrument in a song. If I gave you the file in this custom format, you wouldn’t be able to play it unless I also provided you with software that understood the serialization format I had used.

When an object is to be shared, the receiver needs to understand how the sender will format the object for transmission. There are binary formats for serialization that can’t be understood when looking at the data directly. And there are formats like XML and JSON, where you can read the serialized object and make sense of it with only a little background on these formats. You don’t have to know specifically how every object will be encoded as XML or JSON, because the rules of these formats are well established and will easily apply to many different kinds of objects.

XML and JSON are most commonly used for exchanging data, not for exchanging programmable objects directly. In other words, functions or methods are not usually passed over a wire. You may be familiar with the remoting serialization in .NET that allows objects, including their methods and properties, to be shared across process and machine boundaries, and although you could devise a way to use JSON or XML formats to do this, it would be an atypical use of these formats.

JSON Format

The JSON format leverages a subset of the object literal notation that JavaScript supports natively. In general, the information at www.json.org classifies the notation of objects as being either unordered key-value pairs, or ordered lists of items.

Unordered key-value pairs are separated by colons and surrounded by curly braces. Ordered lists, or arrays, are separated with commas and surrounded by right and left brackets. Individual items or objects are also separated by commas. Look at the following object:

{
    "artist" : "Phish",
    "title" : "A Picture of Nectar",
    "releaseYear" : 1992,
    "tracks" : [
        "Llama", 
        "Eliza", 
        "Cavern",
        "Poor Heart",
        "Stash",
        "Manteca",
        "Guelah Papyrus",
        "Magilla",
        "The Landlady",
        "Glide",
        "Tweezer",
        "The Mango Song",
        "Chalk Dust Torture",
        "Faht",
        "Catapult",
        "Tweezer Reprise"
    ]
}

This object represents an album. It has fields for the artist, title, year of release, and the array of song tracks. Individual elements can be strings, numbers, Booleans, or null. You can see that the JSON syntax is very easy to read and understand. For comparison, I have also represented the album in XML.

<?xml version="1.0" encoding="utf-8" ?>
<album>
  <artist>Phish</artist>
  <title>A Picture of Nectar</title>
  <releaseYear>1992</releaseYear>
  <tracks>
    <track>Llama</track>
    <track>Eliza</track>
    <track>Cavern</track>
    <track>Poor Heart</track>
    <track>Stash</track>
    <track>Manteca</track>
    <track>Guelah Papyrus</track>
    <track>Magilla</track>
    <track>The Landlady</track>
    <track>Glide</track>
    <track>Tweezer</track>
    <track>The Mango Song</track>
    <track>Chalk Dust Torture</track>
    <track>Faht</track>
    <track>Catapult</track>
    <track>Tweezer Reprise</track>
  </tracks>
</album>

The XML format is also readable and easy to understand but has a lot more overhead in terms of raw size than the equivalent JSON. Of course, you can also apply schema to XML for validation of types and apply XSLT transformations to XML more readily than with JSON, but for many applications, this additional flexibility is not needed.

Deserializing JSON-formatted types in the browser has one distinct advantage to working with XML - it is inexpensive and fast to execute. Remember that JSON leverages the object notation that is natively part of JavaScript, so accessing JSON-encoded data is as easy as using the eval operator on it and working with the type that it produces. The eval operator is a general constructor of objects encoded in JSON. Listing 6-16 (Eval.aspx) is a page that wraps the JSON string in parentheses and calls eval on it. The object returned has properties for the artist, title, year of release, and an array of the tracks.

Listing 6-16
Image from book

<!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>Networking</title>
<script type="text/javascript">
var JSONstring = '{' + 
    '"artist" : "Phish",' +
    '"title" : "A Picture of Nectar",'
    '"releaseYear" : 1992,'
    '"tracks" : [' +
    '    "Llama",' +
    '    "Eliza",' +
    '    "Cavern",' +
    '    "Poor Heart",' +
    '    "Stash",' +
    '    "Manteca",' +
    '    "Guelah Papyrus",'
    '    "Magilla",' +
    '    "The Landlady",' +
    '    "Glide",' +
    '    "Tweezer",' +
    '    "The Mango Song",'
    '    "Chalk Dust Torture",'
    '    "Faht",' +
    '    "Catapult",' +
    '    "Tweezer Reprise"'
    ']' +
'}';

function pageLoad() {
    var album = eval("(" + JSONstring + ")");
    var innerHTML = "artist = " + album.artist + "<br />" +
        "title = " + album.title + "<br />" +
        "releaseYear = " + album.releaseYear;
    $get('placeholder').

    var tracks = "";
    for(var i = 0; i < album.tracks.length; i++) {
        tracks += "track #" + i + " = " + album.tracks[i] + "<br />";
    }
    $get('placeholder2').
}

</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div id='placeholder'></div>
    <div id='placeholder2'></div>
    </form>
</body>
</html>
Image from book

Figure 6-4 shows the identical output in Internet Explorer and Firefox. Because the code is just calling the JavaScript eval method on the JSON-formatted object, no special processing code is required for the different browsers.

Image from book
Figure 6-4

JSON Serialization

In the previous example, the JSON string was created explicitly to show the syntax and to demonstrate that calling eval on it results in an object. Most of the time, you aren’t creating the JSON strings in JavaScript code directly. Instead, you will want to create JavaScript objects that correspond to .NET objects created on the server. Thus, the server might create a small .NET entity class that gets serialized into JSON and later deserialized into a JavaScript object in the browser. Thankfully, ASP.NET AJAX makes working with JSON easy. Listing 6-17 (Album.asmx) is a web service that has the equivalent Album .NET class defined earlier in JavaScript. There are properties for title, artist, the year of release, and the album tracks.

Listing 6-17
Image from book

<%@ WebService Language="C#" Class="AlbumProxy" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Net;
using System.Xml.Serialization;
using System.Web.Script.Serialization;

public class Album
{
    private string _artist = String.Empty;
    private string _title = String.Empty;
    private int _releaseYear;
    private string[] _tracks = new string[16];
    private DateTime _dateTime = DateTime.Now;
    private string _personalInfo = "do not show this";

    [ScriptIgnore()]
    public string PersonalInfo
    {
        get {
            return _personalInfo;
        }
        set {
            _personalInfo = value;
        }
    }


    public Album() {
    }

    public string Artist {
        get {
            return _artist;
        }
        set {
            _artist = value;
        }
    }

    public string Title {
        get {
            return _title;
        }
        set {
            _title = value;
        }
    }

    public int ReleaseYear {
        get {
            return _releaseYear;
        }
        set {
            _releaseYear = value;
        }
    }

    public string[] Tracks {
        get {
            return _tracks;
        }
        set {
            _tracks = value;
        }
    }
}

[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class AlbumProxy : System.Web.Services.WebService {

    private Album _album;

    public AlbumProxy() {
        _album = new Album();
        _album.Artist = "Phish";
        _album.Title = "A Picture of Nectar";
        _album.ReleaseYear = 1992;
        _album.Tracks.SetValue("Llama", 0);
        _album.Tracks.SetValue("Eliza", 1);
        _album.Tracks.SetValue("Cavern", 2);
        _album.Tracks.SetValue("Poor Heart", 3);
        _album.Tracks.SetValue("Stash", 4);
        _album.Tracks.SetValue("Manteca", 5);
        _album.Tracks.SetValue("Guelah Papyrus", 6);
        _album.Tracks.SetValue("Magilla", 7);
        _album.Tracks.SetValue("The Landlady", 8);
        _album.Tracks.SetValue("Glide", 9);
        _album.Tracks.SetValue("Tweezer", 10);
        _album.Tracks.SetValue("The Mango Song", 11);
        _album.Tracks.SetValue("Chalk Dust Torture", 12);
        _album.Tracks.SetValue("Faht", 13);
        _album.Tracks.SetValue("Catapult", 14);
        _album.Tracks.SetValue("Tweezer Reprise", 15);
    }

    [WebMethod]
    [XmlInclude(typeof(Album))]
    public object GetAlbum() {
        return _album;
    }

    [WebMethod]
    [XmlInclude(typeof(Album))]
    public object GetAlbumJSON()
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(_album);
    }                
}
Image from book

The web service exposes two methods for retrieving an album instance. If you navigate your browser directly to the album.asmx URL, you will get options for invoking each of the methods as a SOAP service. The GetAlbum method returns the object directly. It is serialized using XML. The GetAlbumJSON method uses the JavaScriptSerializer class to return the album in the JSON format, but when requested as a SOAP service, the JSON is still packed as the value of the XML element, so you’ll see a SOAP XML wrapper around the JSON encoded object. The output from invoking both functions is shown in Figure 6-5. The XML output is shown on the left and the JSON output is shown on the right.

Image from book
Figure 6-5

You can see that the data is fundamentally the same but that the JSON string is more compact, even though it has a simple XML wrapper around it. Sometimes you want the additional flexibility you can get with XML, but for many services, the JSON format is sufficient and smaller. Besides the size advantage of JSON, its use avoids the lack of a standard XML parser being available across all modern browsers.

The properties of the .NET type are serialized into fields of the JavaScript object, as you would expect. Listing 6-18 (ScriptProxy.aspx) includes a ScriptReference to the album.asmx web service. The methods can then be invoked by the proxy objects that ASP.NET AJAX generates for you. The completion function that gets the JSON data can deserialize the result in two steps. First, it applies eval to the string after wrapping it in parentheses to get an album object. Then the deserialize function of the JavaScriptSerializer object is called. The completion function that gets the XML data just applies eval. The parentheses are needed when processing the first results but not the second. This is because ASP.NET AJAX manages the serialization of the return value. When it is called from the generated script proxy, ASP.NET doesn’t actually return the XML, but the JSON formatted object is returned instead.

Listing 6-18
Image from book

<%@Import Namespace="System.Web.Script.Serialization"  %>
<!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>Networking</title>
<script type="text/javascript">

function pageLoad() {
    AlbumProxy.GetAlbumJSON(completionJSON);
    AlbumProxy.GetAlbum(completionXML);
}


function completionJSON(result) {
    var album = eval("(" + result + ")");
    $get('placeholder').innerHTML = album

    var album2 = Sys.Serialization.JavaScriptSerializer.deserialize(result);
    $get('placeholder2').innerHTML = album2.Artist;
}

function completionXML(result) {
    var album = eval(result);
    $get('placeholder3').innerHTML = album.ReleaseYear;
}

</script></head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    <Services>
        <asp:ServiceReference Path="album.asmx" />
    </Services>
    </asp:ScriptManager>
    <div id='placeholder'></div><br
    <div id='placeholder2'></div><br
    <div id='placeholder3'></div>
    </form>
</body>
</html>
Image from book

PageMethods

You saw that a web service class could be decorated with the ScriptService attribute and ASP.NET AJAX would provide JavaScript proxies to invoke it from the browser and manage the types. You can also get proxies for invoking methods contained directly in the page. The method must be a static method. You must also set the EnablePageMethods property of the ScriptManager to true. Listing 6-19 (PageMethods.aspx) includes the method for returning the time on the server directly in the page. The method is marked with both the WebMethod and ScriptMethod attributes. The ScriptMethod attribute is optional. Here it is used to change from the default POST to the GET HTTP verb. The method defined on the server is now available as a member of the PageMethods object on the client. On the client it is a proxy for invoking the method on the server. If it took parameters, they would be the first argument to the JavaScript method, followed by the completion callback, error callback, and user context data.

Listing 6-19
Image from book

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Web.Services" %>
<%@ Import Namespace="System.Web.Script.Services" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    [WebMethod]
    [ScriptMethod(UseHttpGet=true)]
    public static string ServerTime() {
        return DateTime.Now.ToUniversalTime().ToString();
    }
</script>
<script type="text/javascript">
function pageLoad() {
    $addHandler($get('timeButton'), 
}

function getTime() {
    PageMethods.ServerTime(OnServerTimeComplete);
}

function OnServerTimeComplete(result, userContext, methodName) {
    alert(result);
}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>ASP.NET AJAX PageMethod</title>    
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="scriptManager" 
EnablePageMethods="true"></asp:ScriptManager>
    <div>
    <input type="button" value="Show Server Time" id="timeButton" />
    </div>
    </form>
</body>
</html>
Image from book

Working with Dates

The JSON format does not natively support dates. It handles strings, numbers, Booleans, arrays, and nulls but lacks support for Date objects. If a .NET object contains a date, the JavaScriptSerializer class will encode it in a format that the AJAX script library knows how to deserialize in the browser. However, this means that you would now need to use the deserialize method of the Sys.Serialization.JavaScriptSerializer object instead of calling eval directly so that the date will be parsed for you:

var objectWithDate =
Sys.Serialization.JavaScriptSerializer.deserialize(JSONstring);
var date = new Date(objectWithDate.dateProperty);

Bypassing Serialization

Much of the appeal for using the JSON format is due to the streamlined size of encoded objects and the fast speed of working with these types in JavaScript. You have seen that ASP.NET AJAX will automatically turn instances of .NET objects into the JSON format so that they can be deserialized and used in the browser. But since part of the appeal is the reduced size of the JSON format, it follows that you want to be able to exert some control over the serialization behavior to reduce the number of properties you need to serialize. ASP.NET provides the ScriptIgnore attribute to exclude designated properties from being serialized.

Suppose I add a .NET property that I do not want sent to the client in the album object:

private string _personalInfo = "do not show this";

public string PersonalInfo
{
    get {
        return _personalInfo;
    }
    set {
        _personalInfo = value;
    }
} 

The default behavior of the serializer is to include all of the properties available. When looking at the data returned by invoking the object, you see that this new property is included. This is shown in Figure 6-6.

Image from book
Figure 6-6

Adding the ScriptIgnore attribute to a property prevents the JavaScriptSerializer from including it in the serialized form of the object, which will limit the amount of data you send down to the browser:


[ScriptIgnore()]
public string PersonalInfo
{
    get {
        return _personalInfo;
    }

    set {
        _personalInfo = value;
    }
} 

You could argue that the default should be to avoid serializing properties unless they are explicitly designated as being serializable for JSON, but this seemed counter to the expected use of creating simple types for passing from the server to client for manipulation in the browser. In other words, Microsoft opted for ease of use in the general case.

Configuring the JSON Serializer

The serialization examples you have seen here were pretty straightforward, but in real life, the objects you work with are sometimes not so trivial. You rarely care what the time on the server is, and business objects are not usually limited to two or three string properties.

The ASP.NET JavaScriptSerializer can be configured to control its behavior. The default is to limit the depth to which objects are traversed in serializing to one hundred. There is also a limit to the length of the JSON string that is produced when serializing. If the string is longer than 102,400 characters, an error is produced. These limits are in place to guard against inadvertently serializing very large data structures and transmitting them to the client where the ability of the script engine to work with them will be taxed. You probably won’t want to serialize an ADO.NET DataTable having half a million records!

<system.web.extensions>
  <scripting>
    <webServices>
    <jsonSerialization maxJsonLength="102400" recursionLimit="100">
    </jsonSerialization>
    </webServices>
  </scripting>
</system.web.extensions>

Custom Serialization

The ASP.NET AJAX JavaScriptSerializer supports serializing the primitive types that you have seen already in this chapter. But this brute force approach doesn’t scale well to complex types. In practice, you may find that the default serialization is not exactly what you are after. You can shape the server object into the structure that you want to work with in the browser using a custom serialization converter.

You create the custom converter by inheriting from the JavaScriptConverter type in the System.Web.Script.Serialization.Converters namespace. The JavaScriptConverter base type has three public abstract methods to override. The Deserialize method allows you to take in JSON and turn it back into an instance of the supported type. You can override the SupportedTypes method to return the IEnumerable collection of types that can be handled. And the Serialize method is expected to return an IDictionary collection of name-value pairs to include in the JSON output for the object.

You can configure custom converters in the converters element inside the jsonSerialization ele-ment. Here is an excerpt of the configuration from the ASP.NET “Preview” configuration that includes custom converters for the DataSet, DataRow, and DataTable.

    <system.web.extensions>
    <scripting>
      <webServices>
      <jsonSerialization maxJsonLength="500">
        <converters>
            <add name="DataSetConverter" 
type="Microsoft.Web.Preview.Script.Serialization.Converters.DataSetConverter, 
Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35"/>
            <add name="DataRowConverter" 
type="Microsoft.Web.Preview.Script.Serialization.Converters.DataRowConverter, 
Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35"/>
            <add name="DataTableConverter" 
type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter,
  Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35"/>
        </converters>
    </jsonSerialization>
    </webServices>
  </scripting>
</system.web.extensions>