Export (0) Print
Expand All

Programming Ajax and Silverlight Clients

This chapter is excerpted from RESTful .NET: Build and Consume RESTful Web Services with .NET 3.5 by Jon Flanders, published by O'Reilly Media

An Ajax application is a web browser-based application that relies heavily on JavaScript and web services for its functionality. The idea is to bring some of the richness of desktop clients built directly on operating systems to applications built inside a web browser.

Note
Ajax used to stand for Asynchronous JavaScript and XML. However, the industry has decided that Ajax is no longer an acronym, and instead is now a word. It's kind of odd how that happens, but it makes sense in this case because most Ajax applications today use JavaScript, and fewer and fewer use XML, as I'll explain later in the chapter.

Gone are the days when browsers simply made HTTP requests and displayed the resulting web pages. Nowadays, modern browsers support the use of client-side code that allows users to access functions within a web page without having to make additional requests to the server. This code may be JavaScript, or it may be a more sophisticated browser plug-in written in some other language. Applications that use plug-ins are often called rich Internet applications, or RIAs. The calls back to the web server are normally used to get data, which then can be used to update the HTML displayed in the browser, via the browser's API (usually referred to as the HTML Document Object Model, or DOM). These applications are generally user-friendly, as the page in the browser can change and respond to UI requests without having to be refreshed in its entirety, which otherwise could lead to frustrated users.

Note
Ajax applications are not really new (my friend and colleague, John Lam, was helping people build them as long ago as 1998), and Outlook Web Access was arguably the first commercial Ajax application.

WCF 3.5's Web Programming Model supports this model. In fact, Ajax clients might be the most ubiquitous type of REST client, and in this chapter you'll see how you can use the WCF Web Programming Model to implement a variety of Ajax clients.

WCF Web Services and Ajax

Since WCF web endpoints are opened via HTTP, you can call them from JavaScript inside a browser without any modification on the service side. The endpoints provide URI-accessible functionality, so accessing them from an Ajax application is simply a matter of making the right calls using JavaScript (or whatever Ajax library you might be using).

For example, you can call the biology service that you wrote in Chapter 2, WCF RESTful Programming Model from an Ajax page without making changes to the service itself. All you have to do is change the URI of the endpoint so that it will listen on the port your website is exposed on.

Example 7.1, "A simple HTML page with drop-down lists for hierarchical data returned from the biology service" shows the code for a simple HTML page that uses drop-down menus (select elements in HTML) to select the hierarchical data returned from the aforementioned biology service. Calling the service is just a matter of getting the URIs correct for each request, and then parsing the XML that is returned. To create this page, start by adding the HTML shown in Example 7.1, "A simple HTML page with drop-down lists for hierarchical data returned from the biology service" to a web project inside Visual Studio, and then code away.

Example 7.1. A simple HTML page with drop-down lists for hierarchical data returned from the biology service

<!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>
    <title>Using WCF Service from "AJAX"</title>
    <script type="text/javascript">
    function getXmlHttp()
    {
         var xmlHttp;
        try {
            xmlHttp = new XMLHttpRequest();
        } catch (e) {
            try {
                xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    alert("This sample only works in browsers with AJAX support");
                    return false;
                }
            }
        }
        return xmlHttp;
    }
    var serviceURI =  "http://localhost/BioService/";
    function getDomains(){

        var xmlHttp = getXmlHttp();


        xmlHttp.onreadystatechange=function(){
            if(xmlHttp.readyState == 4){
                var doc = xmlHttp.responseXML;
                var nodes  = doc.selectNodes("//Domain");
                var select = document.getElementById("domains");
                var opt = null;
                var name = null;
                var uri = null;
                for(var i=0;i<nodes.length;i++)
                {
                       name = nodes[i].selectSingleNode("Name").text;
                       uri = nodes[i].selectSingleNode("Uri").text;
                       opt = new Option(name,uri,false);
                       select.options[select.options.length] = opt;
                }
            }
        }

        xmlHttp.open("GET", serviceURI, true);
        xmlHttp.setRequestHeader("Content-type", "application/xml");
        xmlHttp.send();

    }

   function selectDomain(el)
   {
        var domainUri = serviceURI  + el[el.selectedIndex].value;
         var xmlHttp = getXmlHttp();


        xmlHttp.onreadystatechange=function(){
            if(xmlHttp.readyState == 4){
                var doc = xmlHttp.responseXML;
                var nodes  = doc.selectNodes("//Kingdom");
                var select = document.getElementById("Kingdoms");
                var opt = null;
                var name = null;
                var uri = null;
                select.options.length = 0;
                for(var i=0;i<nodes.length;i++)
                {
                       name = nodes[i].selectSingleNode("Name").text;
                       uri = nodes[i].selectSingleNode("Uri").text;
                       opt = new Option(name,uri,false);
                       select.options[select.options.length] = opt;
                }
            }
        }

        xmlHttp.open("GET", domainUri, true);
        xmlHttp.setRequestHeader("Content-type", "application/xml");
        xmlHttp.send();

   }
    </script>

</head>
<body onload="getDomains()">
    <h1>Life classification</h1>
    <p>
    Domain:<select id="domains" onchange="selectDomain(this)"></select>
    </p>
    <p>
    Kingdom:<select id="kingdoms"></select>
    </p>
    <p>
    Phylum:<select id="phylum"></select>
    </p>
    <p>
    Class:<select id="class"></select>
    </p>
    <p>
    Order:<select id="order"></select>
    </p>
    <p>
    Family:<select id="family"></select>
    </p>
    <p>
    Genus:<select id="genus"></select>
    </p>
    <p>
    Species:<select id="species"></select>
    </p>
</body>
</html>

The code given in Example 7.1, "A simple HTML page with drop-down lists for hierarchical data returned from the biology service" isn't complex, and is similar to the code you might see inside a typical Ajax page when the service returns XML. Reading through the example, you can see that the code required to call a WCF web endpoint is no different from what you might write to call an exposed endpoint using HTTP.

When this page is loaded, the getDomains JavaScript function will call the root URI of the service, which will return the list of biological domains as XML. The function parses the XML and uses it to populate an option element per Domain inside of the HTML select element.

When the user selects a Domain, another function makes the call to the Kingdom URI, concatenating the URI of Kingdom to the root URI. The selectDomains function then populates the select for Kingdom dynamically, based on the result of the call to the Kingdom URI.

Figures 7.1 and 7.2 show views of typical user interactions with this fairly simple page.

Figure 7.1. Selecting a domain

Selecting a domain

Figure 7.2. Displaying the Kingdom

Displaying the Kingdom

This example isn't really exciting, but it is here to reinforce the notion that WCF web endpoints are general-purpose REST endpoints and can be called by any REST-enabled client, including JavaScript in a browser.

The JavaScript code is also pretty mundane, and you could improve it by encapsulating the XMLHttpRequest in a JavaScript object model. Many such libraries are available for you to download and use, and they're easy to use against a WCF service as well. However, most of these libraries have moved away from XML parsing to JavaScript Object Notation (JSON) serialization as the preferred format for passing data between services and Ajax clients.

The industry has moved to JSON for many reasons, including:

  • JSON has smaller packets because the JSON format is smaller than XML

  • JSON has a more natural programming mode for Ajax clients

  • Parsing JSON is more efficient than parsing XML

Another reason (one that is often left unsaid) is that no one really likes to program against XML APIs in the browser because of the general lack of XML API support (the lack of updates to the XML APIs in browsers is probably a direct result of the popularity of JSON).

Example 7.2, "A simple HTML page after service is ported to use JSON-serialized responses" shows the getDomains function from Example 7.1, "A simple HTML page with drop-down lists for hierarchical data returned from the biology service", rewritten to use JSON-serialized responses (we'll look at the service code in a moment).

Example 7.2. A simple HTML page after service is ported to use JSON-serialized responses

<!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>
    <title>Using WCF Service from "AJAX"</title>
    <script type="text/javascript">
    window.onload = function()
    {
        getDomains();
    }
    function getXmlHttp()
    {
         var xmlHttp;
        try {
            xmlHttp = new XMLHttpRequest();
        } catch (e) {
            try {
                xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    alert("This sample only works in browsers with AJAX support");
                    return false;
                }
            }
        }
        return xmlHttp;
    }
    var serviceURI =  "http://localhost/BioService/";
    function getDomains(){
        var xmlHttp = getXmlHttp();
        xmlHttp.onreadystatechange=function(){
            if(xmlHttp.readyState == 4){
                var result = (eval(xmlHttp.responseText));
                var domain  = null;
                var select = document.getElementById("domains");
                var opt = null;
                var name = null;
                var uri = null;
                for(var i=0;i<result.length;i++)
                {      domain = result[i];
                       name = domain.Name;
                       uri = domain.Uri;
                       opt = new Option(name,uri,false);
                       select.options[select.options.length] = opt;
                }
            }
        }
        xmlHttp.open("GET", serviceURI + "json", true);
        xmlHttp.setRequestHeader("Accept", "application/json");
        xmlHttp.send(null);
    }
   function selectDomain(el)
   {
         var domainUri = serviceURI  + el[el.selectedIndex].value + "/json";
         var xmlHttp = getXmlHttp();
        xmlHttp.onreadystatechange=function(){
            if(xmlHttp.readyState == 4){
                var result = (eval(xmlHttp.responseText));
                var kingdom = null;
                var select = document.getElementById("kingdoms");
                var opt = null;
                var name = null;
                var uri = null;
                for(var i=0;i<result.length;i++)
                {
                       kingdom = result[i];
                       name = kingdom.Name;
                       uri = kingdom.Uri;
                       opt = new Option(name,uri,false);
                       select.options[select.options.length] = opt;
                }
            }
        }
        xmlHttp.open("GET", domainUri, true);
        xmlHttp.setRequestHeader("Accept", "application/json");
        xmlHttp.send(null);
   }
    </script>
</head>
<body>
    <h1>Life classification</h1>
    <p>
    Domain:<select id="domains" onchange="selectDomain(this)"></select>
    </p>
    <p>
    Kingdom:<select id="kingdoms"></select>
    </p>
      <p>
    Phylum:<select></select>
    </p>
    <p>
    Class:<select></select>
    </p>
    <p>
    Order:<select></select>
    </p>
    <p>
    Family:<select></select>
    </p>
    <p>
    Genus:<select></select>
    </p>
    <p>
    Species:<select></select>
    </p>
</body>
</html>

It is true that the code in Example 7.2, "A simple HTML page after service is ported to use JSON-serialized responses" isn't smaller than the code in Example 7.1, "A simple HTML page with drop-down lists for hierarchical data returned from the biology service", but the code in Example 7.2, "A simple HTML page after service is ported to use JSON-serialized responses" is much cleaner. Instead of parsing the response XML DOM, you can just use the JavaScript eval function to parse the return into a full-fledged JavaScript object (in this case, an array of objects that each have a Name and Uri property). Programming against objects is generally preferable to programming against XML (in my experience, most people feel this way), unless there is a big performance hit.

It's often useful to look at the network packets moving between a user agent and a service. Doing so can increase your understanding of how interactions work, and this information can be invaluable when debugging. Many tools are available to do this; I prefer the Web Development Helper from http://www.nikhilk.net/. If you are using Firefox as your browser, Firebug does pretty much the same thing.

Figure 7.3, "JSON size versus XML size using the Web Development Helper" shows the results of using the Web Development Helper, and it's easy to see that JSON is the performance winner. The size of the JSON request is half the size of the XML request, and the response time is also half.

Figure 7.3. JSON size versus XML size using the Web Development Helper

JSON size versus XML size using the Web Development Helper

When a page first loads, the browser makes two requests. The first is for the HTML itself and the second is a call (made by the XMLHttpObject) for the list of Domains (Figure 7.4, "Web Development Helper in browser").

Figure 7.4. Web Development Helper in browser

Web Development Helper in browser

If you click on the second line in the trace box, a Detail dialog box will appear, and you can then click the Response tab to see the response data, encoded as a JavaScript object by default. Figure 7.5, "JSON view of response data" shows the expanded response data in its entirety.

Figure 7.5. JSON view of response data

JSON view of response data

Figure 7.6, "Raw JSON response data" shows the same data, but this time in a text view (which you can select from the Viewer drop-down list). This view shows the actual JSON data as returned from the server.

Figure 7.6. Raw JSON response data

Raw JSON response data

JSON-Enabling a Service Endpoint

How do you program a service endpoint to return JSON instead of XML? One way is to use the WebGet.ResponseFormat property to change a single method at a time. The other way is to use the WebScriptBehavior to modify the whole endpoint. We'll look at both of these techniques in this section.

One approach is to use the ResponseFormat property of the WebGet attribute. This property specifies the format that the serializer will use when deserializing incoming messages and serializing outgoing messages. The default value for this property is WebMessageFormat.Xml, which instructs the serializer to turn your objects into XML. This is the setting that we've seen up to this point.

If you specify WebMessageFormat.Json for the ResponseFormat property, the serializer will serialize objects into JSON format (you can set the ResponseFormat and RequestFormat separately, but in general you'll want them to be the same). Later in this chapter, I'll show you how you can have one operation/method return JSON or XML conditionally.

Example 7.3, "XML and JSON responses" shows the methods that are called for the top two resources (the root resource, represented by the URI /, and the Domain resource, represented by the URI /{Domain}) in the biological taxonomy service hierarchy, modified to return JSON for different URIs.

Example 7.3. XML and JSON responses

//XML responses
[OperationContract]
[WebGet(UriTemplate = "/",ResponseFormat=WebMessageFormat.Xml)]
DomainList GetRoot();
[OperationContract]
[WebGet(UriTemplate = "/{Domain}", ResponseFormat = WebMessageFormat.Xml)]
KingdomList GetDomain(string Domain);
//JSON responses
[OperationContract]
[WebGet(UriTemplate = "/json", ResponseFormat = WebMessageFormat.Json)]
DomainList GetRootJSON();
[OperationContract]
[WebGet(UriTemplate = "/{Domain}/json", ResponseFormat = WebMessageFormat.Json)]
KingdomList GetDomainJSON(string Domain);

Example 7.3, "XML and JSON responses" contains the addition of two methods to the service contract definition (GetRootJSON and GetDomainJSON). These methods have the same return type and parameters as the methods that return XML (GetRoot and GetDomain). To satisfy the CLR compiler, each method must have a different name because the return values and parameters are the same. The method names are irrelevant to WCF, because the WCF web dispatcher cares only about the UriTemplate value associated with each method. The two methods that return JSON include a new path segment on the end of each UriTemplate: "/json". This allows the user agent to get JSON-encoded responses by adding "/json" to the end of the requested URI, or to get XML by leaving "/json" off the requested URI.

Note
In .NET 3.5 SP1, the UriTemplate parsing mechanism has been modified to support URIs with special characters between template parameters inside a path segment. A URI such as http://localhost/BioService/Domain.json could be parsed using a UriTemplate definition such as /{Domain}.{format}. This could allow you to parse out the format automatically and then return JSON or XML. Later in this chapter, we'll discuss when it is possible to return JSON or XML dynamically from the same method.

Any type that can be serialized as XML can also be serialized into JSON, with no additional effort on your part. WCF 3.5 includes a DataContractJsonSerializer that performs this "magic." To make the service work overall, however, we have to add another class-level method for each of the operations, but since each method is returning the same types as the XML versions, we only have to delegate to those versions:

public DomainList GetRootJSON()
{
    return GetRoot();
}

public KingdomList GetDomainJSON(string Domain)
{
    return GetDomain(Domain);
}

If you want to return only JSON from an endpoint, you could use WebScriptEnablingBehavior to make all the operations on the endpoint use JSON as the request and response format. WebScriptEnablingBehavior is an EndpointBehavior that works by automatically modifying all the operations on the configured endpoint to use WebMessageFormat.Json for both the request and the response. This is useful if you want all of your operations to return (and accept) JSON. It is also useful if you want endpoints on one host to use XML and endpoints on another host to use JSON, since you can enable the behavior through configuration, requiring no changes to your code.

Note
If you are hosting in IIS, you can use WebScriptServiceHostFactory as the value of the Factory attribute in your .svc file to further simplify the configuration of a JSON endpoint. It will automatically configure the WebScriptEnablingBehavior for you.

To enable WebScriptEnablingBehavior, add the code in Example 7.4, "Configuring WebScriptEnablingBehavior" to Web.config.

Example 7.4. Configuring WebScriptEnablingBehavior

<behaviors>
    <endpointBehaviors>
        <behavior name="JSONOnly">
            <enableWebScript/>
        </behavior>
    </endpointBehaviors>
</behaviors>
<services>
    <service name="JSONService">
        <endpoint address="/JSON" behaviorC
onfiguration="JSONOnly" binding="webHttpBinding" contract="TheContract"/>
    </service>

Because WebScriptEnablingBehavior derives from WebHttpBehavior, you don't need to add both WebHttpBehavior and WebScriptEnablingBehavior separately; you get a two-for-one effect when adding WebScriptEnablingBehavior. WebScriptEnablingBehavior is the name of the class and enableWebScript is the element name for adding this behavior via the configuration file.

WebServiceHost sh =
    new WebServiceHost(typeof(Service));
Type t = typeof(Service);
Binding b = new WebHttpBinding();
string uri = "http://localhost/webtest/";
ServiceEndpoint se = sh.AddServiceEndpoint(t,b, uri);
se.Behaviors.Add(new WebScriptEnablingBehavior());
sh.Open();

WebScriptEnablingBehavior does have one fairly big restriction: the contracts on the endpoint configured with this behavior can't use UriTemplate to customize the URI-to-method dispatching infrastructure built into WCF 3.5. Instead of the URI customization enabled by using the UriTemplate property on WebGet and WebInvoke, the default URI-to-method dispatching rules will apply. The default rules are that the URI will include the endpoint of the service plus the name of the method. Inputs to a method with a WebGetAttribute will have to be query string parameters (with the query string variable names matching the parameter names). When using WebInvoke instead of WebGet, the same URI and query string rules apply, although the last parameter of the method marked with WebInvoke can still be a complex type (i.e., the deserialized version of the body of the HTTP request).

This restriction might not dissuade you from using WebScriptEnablingBehavior if your clients will be using ASP.NET Ajax, because WebScriptEnablingBehavior will generate a JavaScript proxy that you can use in the JavaScript environment of a browser running the ASP.NET Ajax client runtime. This means that a developer using ASP.NET Ajax in her browser-based application won't have to use the XmlHttpRequest object directly and will have a strongly typed JavaScript object model with which to work against your service.

Note
An extra benefit of this proxy integration is that Visual Studio 2008 is aware of this proxy class and will give you IntelliSense inside your JavaScript code as well.

ASP.NET Ajax is a Microsoft runtime and set of tools that enable developers to build Ajax-based applications in ASP.NET more quickly and easily than if they built raw Ajax applications using JavaScript. It includes a cross-browser JavaScript client (which you can use without using ASP.NET), as well as ASP.NET server-side functionality to help typical ASP.NET developers jumpstart their use of Ajax.

Note
For this section, we will build out the infrastructure of an ASP.NET web application manually, bit by bit, so that you can see how the pieces fit together. Note, however, that Visual Studio 2008 has templates for an Ajax-enabled WCF service, as well as for an Ajax web form, so feel free to use these templates after you have a grasp of what they do.

Let's start by building a service endpoint that can be called by an ASP.NET Ajax client. For consistency, let's continue to use the biological taxonomy service we created in Chapter 3, Programming Read-Only Services (the one that returns resources from the biological taxonomy service as a read-only RESTful service) so that we can contrast the handwritten JavaScript .html page with the ASP.NET Ajax-enabled version. I have an ASP.NET web application named JSONWebTest already added to my local IIS, so we'll build on top of that preexisting project.

First, you need a contract that is compatible with WebScriptEnablingBehavior. In this case, we'll keep our original non-WebScriptEnablingBehavior contract separate (so we can have a more "pure" RESTful endpoint for non-ASP.NET Ajax clients) and we'll add a special one for WebScriptEnablingBehavior.

Instead of creating a separate interface (which you will probably never reuse, since the contract is specialized for this particular ASP.NET application), we'll implement the contract as a class. Example 7.5, "Service contract for BioWrapper, including implementation of two methods" includes the code for the service contract for the BioWrapper endpoint and also includes implementations of two of its methods (GetRoot and GetDomain) rolled into one.

Example 7.5. Service contract for BioWrapper, including implementation of two methods

[ServiceContract(Namespace=")]
public class BioWrapper
{
    [OperationContract()]
    [WebGet()]
    public DomainList GetRoot()
    {
        BioTaxService realImpl = new BioTaxService();
        return realImpl.GetRoot();
    }
    [OperationContract()]
    [WebGet()]
    public KingdomList GetDomain(string Domain)
    {
        BioTaxService realImpl = new BioTaxService();
        return realImpl.GetDomain(Domain);
    }
    //other methods excluded for clarity

}

To keep things simple, Example 7.5, "Service contract for BioWrapper, including implementation of two methods" shows only the top two levels of the BioWrapper service hierarchy. As you can see, the code does not make use of the UriTemplate property of the WebGet attribute. If it did, WCF would throw the following exception:

Endpoints using 'UriTemplate' cannot be used with 'System.ServiceModel.Description.WebScriptEnablingBehavior'.

This is about as straightforward an exception as you'll ever get.

UriTemplate Customization and WebScriptEnablingBehavior

Why doesn't WCF allow UriTemplate customization with WebScriptEnablingBehavior? The underlying ASP.NET Ajax proxy code was already written before WCF came out with the UriTemplate mechanism in .NET 3.5, and supporting UriTemplate customization would have meant changing the underlying JavaScript proxy model. This is annoying, but shouldn't be problematic, since the proxy class hides so much of the functionality anyway.

If you want to have a JSON endpoint that will expose UriTemplate to toolkits other than ASP.NET Ajax, you'll have to wrap the UriTemplate-specific functionality with a new contract that doesn't use UriTemplate specialization.

To get the endpoint up and running, one option is to use a typical WCF .svc file, put it into your virtual directory, and point the Service attribute at the new BioWrapper type. You can then put a service entry into the System.ServiceModel configuration element inside the web.config file with a link to an endpoint behavior element that uses the enableWebScript element (this is the same configuration you saw in the previous section).

Instead of adding that configuration, however, we can take advantage of the fact that .NET 3.5 includes a new ServiceHostFactory-derived type that will automatically configure the service and its endpoint to use that particular configuration. Here are the contents of the .svc file:

<% @ServiceHost
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
Service="BioWrapper"  %>

Pointing the WebScriptServiceHostFactory at the BioWrapper service type allows it to create an instance of the WebScriptServiceHost based on the BioWrapper type. WebScriptServiceHost is similar to the WebServiceHost we discussed in Chapter 5, Hosting WCF RESTful Services. WebScriptServiceHost automatically configures this service with an endpoint using the WebHttpBinding and applying the WebScriptEnablingBehavior to that endpoint. This saves us from having to configure the service in the web.config file.

In order to call this service from a browser using the automatically generated proxy, add the ASP.NET Ajax JavaScript runtime to the browser's JavaScript environment. In this case, we are using an ASP.NET .aspx page on the server to generate the browser resource, so add the appropriate ASP.NET server-side controls to the .aspx file. Most of the time, we think about ASP.NET server-side controls as generating viewable HTML, but they can also generate JavaScript and hidden HTML elements such as the script element.

The ASP.NET page requires a form element with the runat="Server" attribute. This is necessary for setting up the server environment for the other ASP.NET controls that we will add. This element is generally added automatically for you when you create a new .aspx file using Visual Studio. The form element requires a ScriptManager element (also with the runat="Server" attribute). The ScriptManager control generates the JavaScript and script elements, which cause the browser to request the necessary JavaScript files from the server, which loads the ASP.NET Ajax client runtime.

The ScriptManager allows us to add another server-side control to the page: ServiceReference. ServiceReference injects another script element into the ASP.NET Ajax-enabled page. The script element creates another request to the server for a JavaScript file, which contains a JavaScript client "class" that extends the ASP.NET Ajax client proxy class for calling services (which is a JavaScript "class" named System.Net.WebServiceProxy). This new class will be automatically generated based on the .NET metadata of the service (this is in some ways like the automatic proxy generation that many languages have for SOAP-based services using WSDL, except the proxy is generated dynamically at runtime).

The URI for the JavaScript file is added by WebScrip⁠t⁠EnablingBehavior. This WebScrip⁠tEnablingBehavior adds an additional endpoint to the underlying service endpoint. The additional endpoint responds when an HTTP GET request is made to the service endpoint that has the additional "/js" path segment added to the URI (or "/jsdebug" when a debug build is used).

In our case, the JavaScript proxy's URI will be http://localhost/JSONWebTest/BioWrapperService.svc/js (or http://localhost/JSONWebTest/BioWrapperService.svc/jsdebug for debug builds).

Example 7.6, "ASP.NET Ajax page using autogenerated WCF JSON proxy" shows the markup for an .aspx page that puts these features to work.

Example 7.6. ASP.NET Ajax page using autogenerated WCF JSON proxy

<!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>
    <title>Using WCF Service from ASP.NET AJAX</title>

</head>
<body>
<form runat="server">
<asp:ScriptManager runat="server" id="_scriptMan">
<Services>
<asp:ServiceReference Path="~/BioWrapperService.svc" />
</Services>
</asp:ScriptManager>
    <h1>Life classification</h1>
    <p>
    Domain:<select id="domains" onchange="selectDomain(this)"></select>
    </p>
    <p>
    Kingdom:<select id="kingdoms"></select>
    </p>
      <p>
    Phylum:<select></select>
    </p>
    <p>
    Class:<select></select>
    </p>
    <p>
    Order:<select></select>
    </p>
    <p>
    Family:<select></select>
    </p>
    <p>
    Genus:<select></select>
    </p>
    <p>
    Species:<select></select>
    </p>
    </form>
</body>
</html>

We can now modify the JavaScript code to integrate with ASP.NET Ajax. First, modify the window.onload functionality in a method named pageLoad. This is a special method known by ASP.NET Ajax that will be called after all the ASP.NET Ajax context is loaded into the browser. Next, use the syntax of the JavaScript proxy generated by WebScriptEnablingBehavior. Figure 7.7, "WebScriptEnablingBehavior JavaScript proxy" shows a screenshot of that object in the Web Development Helper.

Figure 7.7. WebScriptEnablingBehavior JavaScript proxy

WebScriptEnablingBehavior JavaScript proxy

It's not necessary to dive into the code in detail, but clearly there is a class named BioWrapper, which exposes a number of properties and methods. The two methods we are most interested in are the GetRoot and GetDomain methods that correspond to the methods on our service. The syntax to get the list of domains is BioWrapper.GetRoot.

Warning
Be careful: the class name will also be prefixed by the namespace URI of the ServiceContract. In this case, I explicitly set the namespace to an empty string, which is why the code doesn't need that namespace. The default namespace is tempuri.org, so if I hadn't set the namespace to an empty string, the JavaScript code would use tempuri.org.BioWrapperService. For RESTful service purposes, the namespace is irrelevant, so setting it to an empty string is probably a good practice.

The proxy is inherently asynchronous, so we will specify the JavaScript function that we want called when the initial call completes. The natural parameters to the method would come before the function parameter, but since this particular "method" doesn't have any parameters, the function call comes first:

function pageLoad()
{
    BioWrapper.GetRoot(domainsDone);
}

Next, implement the domainsDone method, shown in Example 7.7, "domainsDone function (JavaScript)", which will be called after the GetRoot asynchronous call completes. This method takes one parameter, which is the result of the asynchronous call. Note that you can also pass a JavaScript object as a context object to the initial call, which can then be passed to the done call. This is useful because the domainsDone call isn't done in the context of the JavaScript this reference (as in Example 7.3, "XML and JSON responses").

Example 7.7. domainsDone function (JavaScript)

function domainsDone(result)
{
    var domain  = null;
    var select = document.getElementById("domains");
    var opt = null;
    var name = null;
    var uri = null;
    for(var i=0;i<result.length;i++)
    {      domain = result[i];
           name = domain.Name;
           uri = domain.Uri;
           opt = new Option(name,uri,false);
           select.options[select.options.length] = opt;
    }
}

The differences between this function and the earlier, non-ASP.NET Ajax version are that this one doesn't call the XmlHttpRequest object (because the generated proxy takes care of that) and that it doesn't parse the response into JSON using eval (because the infrastructure has already done it). The rest of the JavaScript code follows in Example 7.8, "selectDomain function (Javascript)"; I modified it from the earlier version in the same way I modified the code in Examples7.2 and 7.3 by removing the explicit XmlHttpRequest and eval calls.

Example 7.8. selectDomain function (Javascript)

function  selectDomain(el)
{
    var domain = el[el.selectedIndex].value;
    BioWrapper.GetDomain(domain,kingdomDone);

}
function kingdomDone(result)
{
    var kingdom = null;
    var select = document.getElementById("kingdoms");
    var opt = null;
    var name = null;
    var uri = null;
    for(var i=0;i<result.length;i++)
    {
           kingdom = result[i];
           name = kingdom.Name;
           uri = kingdom.Uri;
           opt = new Option(name,uri,false);
           select.options[select.options.length] = opt;
    }
}

It is also interesting to look at the calls the browser made to the server during this interaction. In Figure 7.8, "ASP.NET Ajax page loading" you can see the calls the browser makes when the page first loads.

Figure 7.8. ASP.NET Ajax page loading

ASP.NET Ajax page loading

The last two URLs in Figure 7.8, "ASP.NET Ajax page loading" (in the Web Development Helper pane) are the requests to load the debug version of the proxy and the call to the service endpoint itself, this time to the GetRoot method. This call is made in response to the code inside the pageLoad client-side function. Now look at Figure 7.9, "GetDomain client-side call", which is the page displayed after a Domain is selected from the first drop-down list.

Figure 7.9. GetDomain client-side call

GetDomain client-side call

You can see that the proxy automatically adds the parameter from the call to GetDomain as a query string parameter. The advantage of the WebScriptEnablingBehavior is that when you customize your ServiceContract explicitly for ASP.NET Ajax clients, the programming model the clients must use is much simplified.

Example 7.9, "Full ASP.NET markup and code" pulls the ASP.NET markup and code together into one sample.

Example 7.9. Full ASP.NET markup and code

<!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>
    <title>Using WCF Service from ASP.NET AJAX</title>
    <script type="text/javascript">
function pageLoad()
{
    BioWrapper.GetRoot(domainsDone);
}
function domainsDone(result)
{
    var domain  = null;
    var select = document.getElementById("domains");
    var opt = null;
    var name = null;
    var uri = null;
    for(var i=0;i<result.length;i++)
    {      domain = result[i];
           name = domain.Name;
           uri = domain.Uri;
           opt = new Option(name,uri,false);
           select.options[select.options.length] = opt;
    }
}
function  selectDomain(el)
{
    var domain = el[el.selectedIndex].value;
    BioWrapper.GetDomain(domain,kingdomDone);

}
function kingdomDone(result)
{
    var kingdom = null;
    var select = document.getElementById("kingdoms");
    var opt = null;
    var name = null;
    var uri = null;
    for(var i=0;i<result.length;i++)
    {
           kingdom = result[i];
           name = kingdom.Name;
           uri = kingdom.Uri;
           opt = new Option(name,uri,false);
           select.options[select.options.length] = opt;
    }
}
    </script>

</head>
<body>
<form runat="server">
<asp:ScriptManager runat="server" id="_scriptMan">
<Services>
<asp:ServiceReference Path="~/BioWrapperService.svc" />
</Services>
</asp:ScriptManager>
    <h1>Life classification</h1>
    <p>
    Domain:<select id="domains" onchange="selectDomain(this)"></select>
    </p>
    <p>
    Kingdom:<select id="kingdoms"></select>
    </p>
      <p>
    Phylum:<select></select>
    </p>
    <p>
    Class:<select></select>
    </p>
    <p>
    Order:<select></select>
    </p>
    <p>
    Family:<select></select>
    </p>
    <p>
    Genus:<select></select>
    </p>
    <p>
    Species:<select></select>
    </p>
    </form>
</body>
</html>

Silverlight 1.0 is a cross-browser, cross-platform web browser plug-in that allows you to build interactive applications inside a browser. Silverlight 1.0 is targeted mainly at media display (e.g., videos and images), along with the ability to use JavaScript to interact with the plug-in to enable dynamic applications that can respond to user input.

In essence, Silverlight 1.0 is really an Ajax programming environment, since it doesn't have any executable language. Silverlight 1.0 pages are pure XML (specifically, they are formatted using an XML dialect known as XAML, which stands for eXtensible Application Markup Language).

You use WCF RESTful and JSON services with Silverlight 1.0 in much the same way as you would with ASP.NET Ajax. The difference in using Silverlight 1.0 is that instead of interacting with the HTML DOM, Silverlight 1.0 JavaScript code interacts with the Silverlight 1.0 plug-in.

The more interesting runtime to look at is Silverlight 2.0. Unlike Silverlight 1.0, which is really an Ajax programming environment, Silverlight 2.0 is actually a cross-platform version of the CLR (a subset of the CLR, but with the same programming model and ideas).

Silverlight 2.0 is also a more complete development experience in terms of common programming paradigms such as controls and data binding (unlike Silverlight 1.0, which is geared more toward media playback). Because of this, and because you can write Silverlight 2.0 code in your favorite .NET language, it is a much friendlier environment for calling services.

As in Silverlight 1.0, calling services with Silverlight 2.0 is always asynchronous to prevent the browser's UI from being locked while a service call is being made. For reading from RESTful services (there are actually more facilities for calling SOAP services from Silverlight 2.0), the typical pattern is to use the WebClient class, call either the DownloadStringAsync or OpenReadAsync method, and set up the appropriate delegate, which will be called when the async call is completed.

The WebClient class in Silverlight 2.0 uses the underlying browser functionality (à la XmlHttpRequest from JSON). It allows you to make HTTP requests, although there are some restrictions in terms of its interaction with the full HTTP stack. I'll point these out as we go along.

First, let's use a simple example that isolates WebClient. We will then revisit the biological Domains sample and build a Silverlight application with a little more functionality. For this example, I have created a simple Silverlight application by using the Silverlight project template with an associated Web Application project. I added a Button to the Page.xaml file for invoking the code that uses WebClient and a TextBlock to hold the result (see Figure 7.10, "A simple Silverlight page").

Figure 7.10. A simple Silverlight page

A simple Silverlight page

The following code will run when a user clicks on the button:

private void _getData_Click(object sender, RoutedEventArgs e)
{
    WebClient wc = new WebClient();
    wc.DownloadStringCompleted +=delegate
(object o,DownloadStringCompletedEventArgs args)
    {
        _result.Text = args.Result;

    };
    wc.DownloadStringAsync(new Uri(_uri));
}

You can see that the WebClient programming model is fairly simple. You create a new instance of WebClient, register for the Completed event that is appropriate for the Begin call you are going to make, and then pass a URI to the Begin call. This code uses the DownloadStringAsync Begin call, which makes an HTTP GET request to the URI passed to it, and when the server or service returns the resource at that URI, the WebClient fires the delegate associated with the Completed event. The Completed event passes in an EventArgs type, which contains information about the request; most importantly, it holds onto the result of the call on the aptly named Result property.

This example employs a simple WCF WebGet-enabled service endpoint on the server that returns a string when called. Here is that service:

public class SimpleService : ISimpleService
{

    public string Simple()
    {
        Thread.Sleep(2000);
        return "Simple Silverlight Test";
    }


}

There is a Thread.Sleep in the code so that when you click the button on the Silverlight page, the UI remains responsive. This is why the WebClient API is asynchronous only. Figure 7.11, "Result of WebClient async call" shows the page that is displayed when you click the button.

Figure 7.11. Result of WebClient async call

Result of WebClient async call

Table 7.1, "WebClient API" lays out the rest of the WebClient API. Although DownloadStringAsync was useful in this simple case, we will use the OpenReadAsync method throughout the rest of this chapter to get a Stream as the return value, since a Stream is somewhat more useful when you're trying to parse the format of most resources. All of the WebClient methods have a Progress event as well, which allows you to create UI effects such as progress bars when downloading or uploading large resources.

Table 7.1. WebClient API

Method

Completed event

Progress event

Comment

DownloadStringAsync

DownloadStringCompleted

DownloadStringProgressChanged

Useful for simple cases

OpenReadAsync

OpenReadCompleted

OpenReadProgressChanged

Useful for read-only REST calls

OpenWriteAsync

OpenWriteCompleted

OpenWriteProgressChanged

Useful in simple POST scenarios

UploadStringAsync

UploadStringCompleted

UploadStringProgressChanged

Useful in simple POST scenarios


Parsing XML in Silverlight 2.0

Once you get past the simple cases, you'll need to parse the result of a WebClient call into something useful, which will generally be XML, but might be JSON. In this section, we'll look at the different ways you can program against XML inside Silverlight.

For the rest of the examples in this section we'll use the biological taxonomy service when parsing service results. For this, we will set up another Silverlight application with an associated Web Application project, although the Silverlight application will be invoking the already-existing service endpoint (again, running on the same host and port as the Web Application project; we'll discuss cross-domain access later in this chapter).

There are three basic ways to parse XML in Silverlight 2.0: via XmlReader, XDocument (LinqToXml), or XmlSerialization. For these examples, I've created a Silverlight page with buttons for each of these options. The event handlers for these buttons use the WebClient.OpenReadAsync method to make the appropriate service call, and use different forms of parsing the results in the delegate method associated with the OpenReadCompleted event.

In all cases, we'll parse the results into a list of objects that can be data-bound to a ListBox control in the Silverlight XAML. We will also use a few LINQ queries in the code to further simplify the programming (taking advantage of the fact that Silverlight 2.0 is CLR implementation). We will bind the result of the top of the resource tree to one ListBox (the list of Domains), and we will bind the result of the second level of the hierarchy (the Kingdoms) to another ListBox.

We will use this same page later in this chapter when we discuss how to parse JSON and feed formats, so there are tabs in the page for that functionality. You can see this page in Figure 7.12, "Silverlight page for testing different response formats" (please remember that this book is about REST programming with WCF, and not about how to make a pleasing design with Silverlight; I'm not an accomplished UI expert by any means).

Figure 7.12. Silverlight page for testing different response formats

Silverlight page for testing different response formats

The code samples from this book are available at http://www.rest-ful.net/book, so we won't discuss all of this code in detail here; instead, we will focus only on the pieces that are relevant to our current topic.

Here is the code that is invoked whenever you click one of the buttons:

private void DoRest()
{
    _domainsListBox.DataContext = null;
    _kingdomsListBox.DataContext = null;
    WebClient c = new WebClient();
    c.OpenReadCompleted += DomainComplete;
    c.OpenReadAsync(new Uri(_baseUri));
}

The preceding code simply uses a URI to make a GET request using the WebClient class. The DomainComplete method will be called when the result is available, and we can carry out different types of XML parsing inside that method.

Before getting into the details of this parsing, let me remind you of the format of the resources. Figure 7.13, "Domains XML" shows the top-level XML of the list of Domains, and Figure 7.14, "Kingdoms XML" shows the result from the second level: a list of Kingdoms from a specific Domain. These are here as a reference for you to understand what the code in the following sections is parsing.

Figure 7.13. Domains XML

Domains XML

Figure 7.14. Kingdoms XML

Kingdoms XML

Each button causes a flag to be set in the Silverlight page so that code inside the DomainComplete method will know which kind of parsing to perform. Example 7.10, "DomainComplete method" shows the DomainComplete method in full.

Example 7.10. DomainComplete method

void DomainComplete(object sender,
    OpenReadCompletedEventArgs e)
{
    Stream streamResult = e.Result;

    switch (_currentMode)
    {
        case Mode.XmlReader:
            WriteDomainsXmlReader(streamResult);
            break;
        case Mode.LinqToXML:
            WriteDomainsLinqToXML(streamResult);
            break;
        case Mode.XMlSerializer:
            WriteDomainsXmlSerializer(streamResult);
            break;
        default:
            break;
    }

}

When you click a button, the ListBox will be displayed with the list of Domains. That list is exactly the same no matter which kind of parsing you use; Figure 7.15, "Domains result" shows what the page looks like after you click any of those buttons.

Figure 7.15. Domains result

Domains result

If you were to click on a Domain in the ListBox and then click the name of the Domain, the page would respond by going back to the service to get the list of Kingdoms for that Domain (in the XAML, there is a HyperlinkButton inside each ListItem in the ListBox). When you click a Domain, you will see a page that looks similar to the one shown in Figure 7.16, "Kingdoms result". The event handler for getting the Kingdoms for a particular Domain will use the same form of XML parsing as the first call.

Figure 7.16. Kingdoms result

Kingdoms result

Now that we have set up the basic operation of the sample, let's examine the different parsing methods.

Using XmlReader

Probably the most straightforward way to read XML in Silverlight is to use the familiar XmlReader. For the most part, the XmlReader works the same in Silverlight as it does in the regular CLR. Example 7.11, "Parsing the result from the call to the root of the resource tree" shows the code that will parse the result from the call to the "root" of the resource tree (this is the code that is called in the DomainComplete method when you click the XmlReader button).

Example 7.11. Parsing the result from the call to the root of the resource tree

private void WriteDomainsXmlReader(Stream streamResult)
{
    //create the collection for data binding
    List<BindingClass> bindingContext =
        new List<BindingClass>();
    string uri = null;
    //parse the result stream to an XmlReader
    using (XmlReader xr = XmlReader.Create(streamResult))
    {
        xr.MoveToContent();
        xr.Read();//Move past Domains
        while (xr.Read())
        {
            //pull the Uri result
            if (xr.Name == "Uri" &
                xr.NodeType == XmlNodeType.Element)
            {
                //read out the value of the Uri element
                uri = xr.ReadElementContentAsString();
                //add a new Binding class
                bindingContext.Add(new BindingClass { Text = uri });
            }
        }
    }
    //give the collection to the ListBox for data binding
    _domainsListBox.DataContext = bindingContext;
}

The code is pretty straightforward, following one typical pattern of XmlReader usage, which is to call XmlReader.Read a number of times based on the format of the XML being parsed. In this case, we are currently viewing the Domain elements, so we can just check to see whether the element name is Uri and that the current node isn't the end element. Once we find each Uri element, we can read the value using ReadElementContentAsString and use that value to create a new object to add to the list for data binding against the ListBox. The ListBox will then automatically redraw itself with the appropriate data.

Using XDocument

Another facility Silverlight 2.0 offers for parsing XML is LinqToXml. The LinqToXml API centers on the XDocument type as the container for the XML stream, and allows you to use the LINQ query syntax in your code to derive a set from the XML document itself. The following code will parse the Domain XML resource using LinqToXml.

Note
To use LinqToXml in Silverlight 2.0 you need to add a reference to the System.Xml.Linq.dll assembly in your project.
private void WriteDomainsLinqToXML(Stream streamResult)
{
    XDocument xd =
        XDocument.Load(XmlReader.Create(streamResult));
    var results = from uris in xd.Descendants("Uri")
                  select new BindingClass { Text = uris.Value };

    _domainsListBox.DataContext = results;
}

You can see that this code is significantly more compact than the XmlReader version. This code loads the XML result into an XDocument instance using XDocument.Load. You can then use a LINQ query to get all of the descendant nodes of the document element named Uri in the XML by using XDocument.Descendants. This code uses LINQ to create a new set of BindingClass objects, using the value of the Uri element to set the Text property of the newly created BindingClass instances. Again, this collection is given to the ListBox, which will automatically update the UI based on this new data.

Using XmlSerialization

The XmlSerialization API in Silverlight 2.0 is similar to the same API in the "regular" CLR: it allows you to serialize an object into XML, or to serialize XML into a live object. Here is the code from the Silverlight page that uses the XmlSerializer:

private void WriteDomainsXmlSerializer(Stream streamResult)
{
    Domains domains = (Domains)_domainSerializer.Deserialize(streamResult);
    var results = from domain in domains.Domain
                  select new BindingClass { Text = domain.Uri };
    _domainsListBox.DataContext = results;

}

This code uses LINQ to build the list of objects for the ListBox to bind to. Note that, as in the "regular" CLR, XmlSerializer.Deserialize returns an object that must be cast into the type that fits the shape of the incoming XML stream. How, then, did I get this type definition (in this case, a class named Domains for the collection of Domain objects), since the RESTful service doesn't have any metadata from which such a definition could be generated (à la WSDL from a SOAP service endpoint)?

I went through a few manual but very easy steps. First, I used the browser to invoke the service, and then I entered a View Source command in the browser to get the XML text, which I copied into an XML file inside Visual Studio 2008. Visual Studio provides an XML menu when you are editing an XML file, and from that menu I selected Create Schema. Visual Studio then generated an XSD schema file for this XML. Next, I used the XSD.exe command-line tool against the XSD to generate the class (using the /c command-line switch).

Note
In order to use the XmlSerializer object in Silverlight 2.0 you need to add a reference to the System.Xml.Serialization.dll assembly in your Silverlight project.

XML parsing wrap-up

You are now familiar with the three basic options for parsing raw XML results in Silverlight 2.0. You should pick whichever of those options is most comfortable for you as a programmer, although I tend to prefer the LinqToXml approach since it generally results in the most compact code.

Parsing JSON in Silverlight 2.0

Another format you may run into when programming using Silverlight 2.0 is JSON. Many services are intended for use from multiple web clients. Earlier in the chapter we discussed the advantage of using JSON as your resource format when building REST services, and these apply just as much to a Silverlight application.

Note
In order to use the JsonObject in Silverlight 2.0 you need to add a reference to the System.Json.dll.assembly in your project.

In Silverlight 2.0, there is a JSON serialization/deserialization layer centered on a class named, appropriately, JsonObject. Unlike the JSON usage we saw earlier with JavaScript clients, this is a weakly typed object model, because Silverlight 2.0 code is compiled instead of interpreted. For parsing JSON, we can set up a different tab in the Silverlight page, but the functionality is exactly the same as the XML parsing tab: when you click the Use Json button, the code uses WebClient to call to a RESTful endpoint using an HTTP GET, and on the return, the stream is JSON-encoded. The code will then parse the JSON stream using JsonObject.Load. In this case, JsonObject.Load returns a JsonArray object that holds onto an array of Domain objects, since this is the format of the returned resource (in other cases, JsonObject.Load may return only a single JsonObject). Example 7.12, "Silverlight event handler code" shows the three methods that, working together, provide this functionality.

Example 7.12. Silverlight event handler code

//event handler for JSON button
private void _domainJSON_Click(object sender, RoutedEventArgs e)
{

    HyperlinkButton button = sender as HyperlinkButton;
    TextBlock text = button.Content as TextBlock;
    string domain = text.Text;
    ProcessDomainJSON(domain);
}
//WebClient call for JSON
private void ProcessDomainJSON(string domain)
{
    WebClient c = new WebClient();
    c.OpenReadCompleted += DomainCompleteJson;
    c.OpenReadAsync(new Uri(_baseUri + domain + "/json"));
}
//Complete event handler for JSON
void DomainCompleteJson(object sender,
   OpenReadCompletedEventArgs e)
{
    Stream streamResult = e.Result;
    JsonArray json = (JsonArray)JsonObject.Load(streamResult);
    var result = from j in json
                 select new BindingClass { Text = j["Uri"] };
    _domainsListBoxJSON.DataContext = result;
}

Again, LINQ provides a fair amount of help in taking the result set and turning it into the list of objects against which the ListBox control can data-bind.

Consuming Feeds in Silverlight 2.0

As discussed in Chapter 6, Programming Feeds, web feeds are quickly becoming a popular way to expose various types of data (not just blogs). In that chapter, we wrote a service using WCF that exposes the data from a computer's event log using a RESTful API approach. If you look at the Silverlight example in Figure 7.17, "Silverlight Feeds tab after button is clicked", which admittedly is a very simple UI, the Silverlight code will consume the event log feed when you click the button on the third tab.

Figure 7.17. Silverlight Feeds tab after button is clicked

Silverlight Feeds tab after button is clicked

Since feeds are such a ubiquitous and well-known XML format, instead of parsing feed data using one of the three XML parsing approaches, Silverlight 2.0 uses the same feed object model as WCF does in the "regular" CLR. This model is based on the SyndicationFeed class. Example 7.13, "Parsing feed data in Silverlight" shows the code that supports the third tab of the page.

Example 7.13. Parsing feed data in Silverlight

//event handler for button
private void _feed_Click(object sender, RoutedEventArgs e)
{
    WebClient c = new WebClient();
    c.OpenReadCompleted += FeedComplete;
    c.OpenReadAsync(new Uri(_feedUri));
}
//called when feed is delivered
void FeedComplete(object sender,
OpenReadCompletedEventArgs e)
{
    Stream streamResult = e.Result;
    XmlReader xr = XmlReader.Create(streamResult);
    SyndicationFeed feed =
        SyndicationFeed.Load(xr);
    var result = from item in feed.Items
                 select new BindingClass
                 {
                     Text =
                     ((TextSyndicationContent)item.Content).Text
                 };
    _feedListBox.DataContext = result;
}

The same basic object model for SyndicationFeed exists for Silverlight 2.0 (see Chapter 6, Programming Feeds for more information about the SyndicationFeed API).

OpenWriteAsync in Silverlight 2.0

The WebClient API in Silverlight 2.0 also includes the ability to send POST data to HTTP endpoints. Unfortunately, to support multiple browser plug-in models, the WebClient doesn't allow you to change the HTTP method. Using Silverlight 2.0 against a RESTful service that implements more than just the GET and POST parts of the uniform interface is problematic. This means that, to support Silverlight 2.0 clients fully, you will have to deal with the other parts of the uniform interface (PUT and DELETE) through POST (à la a SOAP service).

Cross-Domain Security in Silverlight 2.0

One consideration when using web-based applications is cross-domain security. It can be dangerous for clients whose pages came from one domain to call into a service from another domain.

Silverlight 2.0 will look for either a clientaccesspolicy.xml or a crossdomain.xml file at the root of your website's virtual directory. (Silverlight 2.0 supports a subset of the crossdomain.xml schema). Here is a clientaccesspolicy.xml file that enables access from all client domains to all services in your virtual directory:

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Adding restrictions to the domain element or resource elements can restrict the access of Silverlight 2.0 clients to your services.

For the crossdomain.xml file, Silverlight 2.0 will respond correctly to the file only if it allows full access from any domain:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
 "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>

Again, Silverlight 2.0 was in beta at the time of this writing, so please verify these settings with the current Silverlight 2.0 documentation.

In this chapter, you've seen the power of JSON from an Ajax programming point of view. WCF provides a nice model for returning either JSON or XML from a particular method on your RESTful service class, but there isn't an easy way to make a method return either JSON or XML conditionally.

Generally, you'd like to return JSON or XML based on one of two constructs. Clients send an Accept HTTP header when making requests of your RESTful endpoints. It would be nice to be able to return JSON when the Accept header value is "application/json" and XML when the Accept header value is "text/xml". Another thing to consider is supporting different URIs for each resource format (e.g., http://server/Resource for the XML version and http://server/Resource/json for a JSON-encoded resource). WCF actually supports the latter fairly well because it is easy to add another method to your ServiceContract and specify the same UriTemplate value as the XML resource URI, but with "/json" concatenated at the end. This is what we did for the biological taxonomy service used earlier in this chapter. This requires a bit of hand-coding, but in the end it is fairly easy to build because the JSON version of your method can just call the XML version, making the JSON version a shim that is necessary for the WCF web infrastructure. Supporting this approach was actually necessary for the examples in this chapter since you can't change the Accept header of the Silverlight 2.0 WebClient object.

Supporting the former approach is possible in WCF, but it will require a bit more heavy lifting on your part when you write the code for your methods. To return JSON or XML based on the Accept header, you have to write your methods in WCF to return the System.ServiceModel.Channels.Message type as your return parameter. For WebInvokeAttribute methods, the HTTP body parameter will have to be Message. Note that you can still use UriTemplate even when you are using this generic message serialization functionality.

A special message property is added during the execution of a web request in WCF (when using the WebHttpBehavior) that tells the underlying serialization infrastructure whether to use DataContractSerializer, XmlSerializer, or DataContractJsonSerializer for the message. This special message property is WebBodyFormatMessageProperty. The WebBodyFormatMessageProperty has a property named Format, which is an enumeration value of type WebContentFormat. The values of this enumeration are given in Table 7.2, "WebContentFormat values".

Table 7.2. WebContentFormat values

Value

Description

Default

The message formatter can't be determined

Xml

The message will be formatted using XML

Json

The message will be formatted using JSON

Raw

The message will be treated as a raw stream (used when the type of the parameter is Stream)


When strongly typed messages (i.e., not System.ServiceModel.Channels.Message) are used, the value of this WebContentFormat is configured for both the parameters and the return values of each operation as the ServiceHost is opening its communication infrastructure. This property becomes read-only and can't be changed dynamically at runtime.

On the other hand, when using Message as the input and output type for your methods, you can set this property dynamically based on whatever condition you like. The code in Example 7.14, "Using Message as the return value" is a rewrite of the two methods that relate to the top two resources in the biological taxonomy service hierarchy, using Message as the return value. Note that we can still use the strongly typed objects to create the message; you just need to create a WCF Message object by passing in the object to the Message.CreateMessage factory method.

Example 7.14. Using Message as the return value

//"loosely" typed top-level method
public Message GetRoot()
{
    DomainList list = new DomainList();
    string[] domains = new string[] { "Archaea", "Eubacteria", "Eukaryota" };
    foreach (string domain in domains)
    {
        list.Add(new Domain { Name = domain, Uri = domain });

    }
    Message ret = CreateMessage(list);
    return ret;
}
//"loosely" typed method to get Kingdoms
public Message GetDomain(string Domain)
{
    KingdomList list = new KingdomList();
    switch (Domain)
    {
        case "Eukaryota":
            string[] kingdoms = new string[] { "Animalia", "Fungi",
 "Amoebozoa", "Plantae", "Chromalveolata", "Rhizaria", "Excavata" };
            list.AddRange((from s in kingdoms
                           select new Kingdom { Name = s, Uri = s }));
            break;
        default:
            break;
    }
    Message ret = CreateMessage(list);
    return ret;
}
//method to create Message object
Message CreateMessage(object msg)
{
    //find the right serializer
    XmlObjectSerializer serializer = SetSerializer(msg);
    //create the message
    Message ret = Message.CreateMessage(MessageVersion.None,
                                     "*",
                                        msg, serializer);
    return ret;

}
//method that looks at the accept header to
//determine the right serializer
XmlObjectSerializer SetSerializer(object msg)
{
    XmlObjectSerializer ret = null;
    if (WebOperationContext.Current.IncomingRequest.Accept == "application/json")
    {
        //set up the right formatter for the message
        WebBodyFormatMessageProperty formatter =
            new WebBodyFormatMessageProperty(WebContentFormat.Json);
        OperationContext.Current.OutgoingMessageProperties.Add(
            WebBodyFormatMessageProperty.Name,
            formatter);
        //set the right content-type header
        WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
        //create the right serializer
        ret = new DataContractJsonSerializer(msg.GetType());
    }
    else
    {
        //create the normal XML serializer
        ret = new DataContractSerializer(msg.GetType());
    }
    return ret;
}

The really important code in Example 7.14, "Using Message as the return value" is in the last method, SetSerializer, which is a method we can use to dynamically determine, based on the value of the incoming Accept header, which serializer to use for the outgoing message.

Note
We can't simply set this message property on the OperationContext.Cur⁠rent.OutgoingMessageProperties because when a strongly typed message is used, the WCF serialization infrastructure is statically created to do either JSON or XML for a particular operation, and won't respond correctly on a case-by-case basis based on this value being in the Message. When the loosely typed Message type is used, however, all of the serialization is done based on the Message itself, without regard for how the operation was set up.

In this chapter, we looked at how the Web Programming Model in WCF 3.5 extends its reach to multiple clients. The ability to use WCF RESTful services from Ajax is one level of that reach, but the ability to deal with the JSON serialization format helps to extend that reach even further, since so many programming environments provide support for calling services that return and accept JSON.

We also looked at how the WebScriptEnablingBehavior element tightly integrates WCF services and the ASP.NET Ajax programming environment. Finally, the RIA environment of Silverlight brings a whole new dimension to web programming, and the WCF RESTful services (including feed support) built into the Silverlight 2.0 programming model make building interactive web applications even easier.

Show:
© 2014 Microsoft