One of the most attractive features of WCF is that you get to write a service once and expose it to be used by multiple clients using different technologies and protocols. How a WCF service is exposed can be changed simply by changing the configuration file, without needing to recompile the code. In this article, we're going to create a WCF service and expose it to Ajax. We'll test the Ajax service by calling it from ASP.NET Ajax 4.0 and jQuery. We'll then expose the service to Silverlight through binary encoding, which will increase data transfer efficiency. Finally, we'll create a small Silverlight 3.0 application that will call the service.

Note: I'm using VS 2010 RC, ASP.NET Ajax 4.0, jQuery 1.4.2 and Silverlight 3.0. Everything WCF related should work similarly for other versions, but some changes may be necessary. Features specific to a version (for example client templates in ASP.NET Ajax 4.0) will obviously not work in previous versions.

The Service

Open up VS 2010 and create a new project. I'm using the ASP.NET Empty Web Application template. Name the project WCFEndpoints.

NewProject

This gives us an empty ASP.NET application with the following (very simple) web.config:

   
<?xml version="1.0"?> 
  <!--    
  For more information on how to configure your ASP.NET application, please visit     
http://go.microsoft.com/fwlink/?LinkId=169433     
  --> 
  <configuration>    
    <system.web>     
        <compilation debug="true" targetFramework="4.0" />     
    </system.web> 
  </configuration>   
    

Add a folder called Model to the project and add the following two classes:

   
using System;     
    
namespace WCFEndpoints.Model     
{     
    public class Book     
    {     
        public int BookId { get; set; }     
        public string Title { get; set; }     
        public string Author { get; set; }     
        public decimal Price { get; set; }     
        public DateTime PublishDate { get; set; }     
    }     
}    
    

 

   
using System;     
using System.Collections.Generic;     
using System.Linq;     
    
namespace WCFEndpoints.Model     
{     
    public class BookService     
    {     
        public IEnumerable<Book> GetBooks()     
        {     
            yield return new Book { BookId = 1, Title = "LOTR", Author = "Tolkien", Price = 180.00M, PublishDate = new DateTime(1954, 1, 1) };     
            yield return new Book { BookId = 2, Title = "C#", Author = "Hejlsberg", Price = 280.00M, PublishDate = new DateTime(2003, 1, 1) };     
            yield return new Book { BookId = 3, Title = "DDD", Author = "Evans", Price = 200.00M, PublishDate = new DateTime(2003, 8, 30) };     
            yield return new Book { BookId = 4, Title = "Architecture", Author = "Fowler", Price = 250.00M, PublishDate = new DateTime(2002, 11, 15) };     
        } 
          public IEnumerable<Book> GetBooksPublishedAfter(DateTime date)    
        {     
            return GetBooks().Where(x => x.PublishDate > date);     
        }     
    }     
}    
    

Note: I'm not exactly following best practices in terms of the model. I just wanted to keep that part simple as it's not the focus of the article.

Right click the WCFEndpoints project (not solution) in the solution explorer and select "Add New Item". From there, add a new "AJAX-enabled WCF Service" called LibraryService.

AddService

 

Open the newly generated LibraryService.svc.cs file and modify it so that it looks like this:

   
using System;     
using System.Collections.Generic;     
using System.Linq;     
using System.ServiceModel;     
using System.ServiceModel.Activation;     
using WCFEndpoints.Model;     
    
namespace WCFEndpoints     
{     
    [ServiceContract(Namespace = "Library")]     
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]     
    public class LibraryService     
    {     
        BookService svc = new BookService(); 
          [OperationContract]    
        public List<Book> GetAllBooks()     
        {     
            return svc.GetBooks().ToList();     
        } 
          [OperationContract]    
        public List<Book> GetBooksPublishedAfter(DateTime date)     
        {     
            return svc.GetBooksPublishedAfter(date).ToList();     
        }     
    }     
}    
    

That's all we are going to write for the service. The service is currently usable from an Ajax client and we'll only need to change the configuration in web.config to expose it to Silverlight. Let's take a quick look at the auto generated WCF specific section the VS created for us when adding the service:

   
<configuration>     
    <system.web>     
        <compilation debug="true" targetFramework="4.0" />     
    </system.web> 
      <system.serviceModel>    
        <behaviors>     
            <endpointBehaviors>     
                <behavior name="WCFEndpoints.LibraryServiceAspNetAjaxBehavior">     
                    <enableWebScript />     
                </behavior>     
            </endpointBehaviors>     
        </behaviors>     
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"     
            multipleSiteBindingsEnabled="true" />     
        <services>     
            <service name="WCFEndpoints.LibraryService">     
                <endpoint address="" behaviorConfiguration="WCFEndpoints.LibraryServiceAspNetAjaxBehavior"     
                    binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />     
            </service>     
        </services>     
    </system.serviceModel>     
</configuration>    
    

The WCF sepcific section is the part inside the <system.serviceModel> tags. The first change we're going to do is to rename "WCFEndpoints.LibraryServiceAspNetAjaxBehavior" to "AjaxBehavior". Just make sure you change both instances. Next, we're going to add a service behaviour and assign it to our service. The service behaviour we're adding will allow us to publish the service metadata via HTTP GET. We will also change the address of the endpoint to "ajax" (I will explain this later).  After these changes, the <system.serviceModel> section will look like this:

   
<system.serviceModel>     
        <behaviors>     
            <endpointBehaviors>     
                <behavior name="AjaxBehavior">     
                    <enableWebScript />     
                </behavior>     
            </endpointBehaviors>     
          <serviceBehaviors>     
            <behavior name="metadataGetBehavior">     
              <serviceMetadata httpGetEnabled="true"/>     
              <serviceDebug includeExceptionDetailInFaults="true"></serviceDebug>     
            </behavior>            
          </serviceBehaviors>     
        </behaviors>     
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"     
            multipleSiteBindingsEnabled="true" />     
        <services>     
            <service name="WCFEndpoints.LibraryService" behaviorConfiguration="metadataGetBehavior">     
                <endpoint address="ajax" behaviorConfiguration="AjaxBehavior"     
                    binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />     
            </service>     
        </services>     
    </system.serviceModel>    
    

Save the config file, right click the LibraryService.svc file in solution explorer and select "View in Browser". If everything went ok, you should see something like this:

browserTest

 

Ajax Test 1: ASP.NET Ajax

I'll be using the latest release of ASP.NET Ajax (currently 0911 beta). If you don't have it, download it from http://ajax.codeplex.com/, extract the zip, create a new folder called Scripts in the main project, add a subfolder called MSAjax and paste in everything inside the Scripts folder of the extracted zip to the MSAjax folder. Add a new htm file called Index.htm to the project. I'm using a plain htm file to show the pure client side aspects of ASP.NET Ajax. First, we're going to create a client side template:

   
<fieldset>     
    <legend>All Books</legend>     
    <div>     
        <ul id='books-template' class='sys-template'>     
            <li>{{Title}} - {{Author}}, {{ String.format("${0:n}", Price) }}, {{ PublishDate.format("dd-MMM-yyyy")     
                }} </li>     
        </ul>     
    </div>     
</fieldset>    
    

Next, add the following style declaration:

   
<style type="text/css">     
       .sys-template, .hidden     
       {     
           display: none;     
       }     
</style>    
    

Add a reference to Start.js:

   
<script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>    
    

Add the following javascript in a <script> tag inside the <head>:

[code:js]
Sys.require([Sys.scripts.WebServices, Sys.components.dataView, Sys.scripts.Globalization], function () {
           Sys.create.dataView("#books-template", {
               'dataProvider': '/LibraryService.svc/ajax',
               'fetchOperation': 'GetAllBooks',
               'autoFetch': true
           });
       });

What this piece of code is doing is it's loading the scripts necessary for calling web services, using the dataview and formatting asynchronously and when all are loaded, it's creating a dataview that calls out to our service's GetAllBooks method. When it gets back the result, it uses our template to add a <li> for each Book returned. Notice that for the dataProvider property, I specify '/LibraryService.svc/ajax'. We specified "ajax" as the address for our service's Ajax endpoint in web.config – this is where that comes in. Had we kept it empty, we would have set the dataProvider to '/LibraryService.svc'. Since our endpoint's address is "ajax", we're specifying 'LibraryService.svc/ajax'. The reason we're doing this is that later on we'll add another endpoint that will use binary encoding to be consumed by Silverlight 3.

At this point, if you run the page, you should see something like this:

results1

Look at that – less than 10 lines of html and very little javascript and you're displaying a formatted list of results got from a web service call. And you had to do zero html building in javascript. Splendid :)

Let's take this one step further and add a calendar which would allow the user to select a date and a button that, when clicked, will call the 'GetBooksPublishedAfter' method of our web service and display only the books that were published after the selected date. Add the following html right after the closing </fieldset> tag:

   
<br />     
   <fieldset>     
       <legend>Books Published After:</legend>     
       <div>     
           Please select a date:     
           <input type='text' id='txtDate' /><br />     
           <input type='button' id='btnFilter' value='fetch books' />     
       </div>     
       <div>     
           <ul id='filtered-books'>     
               <li class='hidden'></li>     
           </ul>     
       </div>     
   </fieldset>    
    

We want to attach a calendar, which is part of the Ajax Control Toolkit, to the textbox. In order to do so, we need to ensure that the script loader can load the calendar script on demand and that the css for the calendar is loaded. Add the following two lines right after the registration of Start.js:

   
<script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>     
<link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />    
    

The Start.js file has logic to load scripts in parallel and on-the-fly as well as data about the core Ajax Library scripts and jQuery. It does not contain data about the toolkit controls. This data is present in ExtendedControls.js. Unfortunately, the script loader currently cannot load css at runtime. It is a feature the ASP.NET Ajax team are looking into. For the time being, we'll need to specify the css file explicitly.

To attach a calendar to the textbox, add the following javascript:

[code:js]
Sys.require([Sys.components.calendar], function () {
           var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
           Sys.create.calendar('#txtDate', {
               'format': 'dd-MM-yyyy',
               'selectedDate': selectedDate,
               'id': 'cal-txtDate'
           });
       });

Finally, we need to bind some code to the button's click handler so that when it's clicked, our web service will be called and the UI updated:

[code:js]
Sys.addHandler('#btnFilter', 'click', function () {
               var date = $find('cal-txtDate').get_selectedDate();
               Sys.create.dataView('#filtered-books', {
                   'dataProvider': '/LibraryService.svc/ajax',
                   'fetchOperation': 'GetBooksPublishedAfter',
                   'fetchParameters': { 'date': date },
                   'itemTemplate': '#books-template',
                   'autoFetch': true
               });
           });

Notice that we're using our previously declared template and passing it in via the itemTemplate parameter. This is quite nice (and DRY) and ensures that we don't have to repeat the template just because the data changes. Since the code is getting slightly complex, here's the whole page for your convenience:

   
<!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></title>     
    <style type="text/css">     
        .sys-template, .hidden     
        {     
            display: none;     
        }     
    </style>     
    <script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>     
    <script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>     
    <link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />     
    <script type='text/javascript'> 
          //initialize the first dataview    
        Sys.require([Sys.scripts.WebServices, Sys.components.dataView, Sys.scripts.Globalization], function () {     
            Sys.create.dataView("#books-template", {     
                'dataProvider': '/LibraryService.svc/ajax',     
                'fetchOperation': 'GetAllBooks',     
                'autoFetch': true     
            });     
        }); 
          //initialize the calender and button handler    
        Sys.require([Sys.components.calendar], function () {     
            var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');     
            Sys.create.calendar('#txtDate', {     
                'format': 'dd-MM-yyyy',     
                'selectedDate': selectedDate,     
                'id': 'cal-txtDate'     
            }); 
              //add the handler, call the service when the button is clicked and update the UI.    
            Sys.addHandler('#btnFilter', 'click', function () {     
                var date = $find('cal-txtDate').get_selectedDate();     
                Sys.create.dataView('#filtered-books', {     
                    'dataProvider': '/LibraryService.svc/ajax',     
                    'fetchOperation': 'GetBooksPublishedAfter',     
                    'fetchParameters': { 'date': date },     
                    'itemTemplate': '#books-template',     
                    'autoFetch': true     
                });     
            });     
        }); 
      </script>    
</head>     
<body>     
    <fieldset>     
        <legend>All Books</legend>     
        <div>     
            <ul id='books-template' class='sys-template'>     
                <li>{{Title}} - {{Author}}, {{ String.format("${0:n}", Price) }}, {{ PublishDate.format("dd-MMM-yyyy")     
                    }} </li>     
            </ul>     
        </div>     
    </fieldset>     
    <br />     
    <fieldset>     
        <legend>Books Published After:</legend>     
        <div>     
            Please select a date:     
            <input type='text' id='txtDate' /><br />     
            <input type='button' id='btnFilter' value='fetch books' />     
        </div>     
        <div>     
            <ul id='filtered-books'>     
                <li class='hidden'></li>     
            </ul>     
        </div>     
    </fieldset>     
</body>     
</html>    
    

Running the page, you should see the textbox initialized to 1-1-2000. Clicking in the textbox, you should see a nice little popup calendar which lets you select a date. Clicking on the button will show you the results of the filtered query. This should look something like this:

results2

 

Ajax Test 2: jQuery

Add a new page for the jQuery test. Call it Index2.htm. Add the following html:

   
<fieldset>     
       <legend>All Books</legend>     
       <div id='all-books'></div>     
</fieldset>    
    

We'll also add the script loader in ASP.NET Ajax, so add the following script reference:

   
<script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>    
    

Download the latest version of jQuery (currently 1.4.2) from www.jquery.com and add it to the Scripts folder. With that done, add the following javascript:

[code:js]
Sys.loadScripts(['/Scripts/jquery-1.4.2.js'], function () {

           $(function () {
               $.post('/LibraryService.svc/ajax/GetAllBooks', null, function (res) {
                   Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                       res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                       var data = res.d; //data is in d
                       var list = $('<ul></ul>');

                       for (var i = 0; i < data.length; i++) {
                           list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                       }
                       $('#all-books').empty().html(list);
                   });
               }, 'text');
           });

       });

I'm loading jQuery via the script loader and making a $.post to the web service. I'm using POST as that's what WCF supports out of the box for web service calls. In the callback function, I'm cheating slightly as I'm using the ASP.NET Ajax JavaScriptSerializer to deserialize the response. There are other ways of deserializing the response (check out http://www.west-wind.com/weblog/posts/324917.aspx), but using the ASP.NET Ajax deserializer is so simple and easy, I decided to use that one. Do we need to deserialize all the time? Well, no. You would need it when returning any DateTime object from WCF. JSON doesn't have a native way to express dates. ASP.NET Ajax has a way of serializing dates to strings so that they can be deserialized to a javascript Date object if the serializer being used knows about it. And the Sys.Serialization.JavaScriptSerializer does know about it. I'm also referencing Sys.scripts.Globalization to be able to format the price and publish date information.

Another thing to notice is that in the $.post call, I'm mentioning 'text' as the type of data returned. The reason I'm specifying 'text' and not 'json' is that we're deserializing the response right after getting the response. If we had specified 'json', we would be trying to deserialize a javascript object, which doesn't make sense.

Lastly, after deserializing the result, we're only working with the result's "d" member. The reason for this is that WCF envelopes all returned data into a variable called "d". So, we get our books array in res.d and not res. The "d" envelope is used to prevent some javascript exploits.

If we run the page now, we should see this:

results3

 

For the next part, add the following html after the closing </fieldset> tag:

   
<fieldset>     
       <legend>Filtered Books</legend>     
       <div>     
           Please select a date:     
           <input type='text' id='txtDate' /><br />     
           <input type='button' id='btnFilter' value='fetch books' />     
       </div>     
       <div id='filtered-books></div>     
</fieldset>    
    

Add the calender to the textbox just like we did before. Since we're already using jQuery, this becomes even easier. Add the reference to ExtenderControls.js and the Calendar.css:

   
<script src="/Scripts/MSAjax/extended/ExtendedControls.js" type="text/javascript"></script>     
<link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />    
    

The following javascript will then attach a calendar to the textbox:

[code:js]
Sys.require([Sys.components.calendar], function () {
                var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');
                $('#txtDate').calendar({
                    'format': 'dd-MM-yyyy',
                    'selectedDate': selectedDate,
                    'id': 'cal-txtDate'
                });
            });

We now need to add the event handler so that clicking the button will fire the service call and update the UI:

[code:js]
$('#btnFilter').click(function () {
                        var date = $find('cal-txtDate').get_selectedDate();
                        var params = { 'date': date} ;
                        var serializedParams = Sys.Serialization.JavaScriptSerializer.serialize(params);
                        $.ajax({
                            'url': '/LibraryService.svc/ajax/GetBooksPublishedAfter',
                            'data': serializedParams,
                            'type': 'POST',
                            'processData': false,
                            'contentType': 'application/json',
                            'timeout': 10000,
                            'dataType': 'text', //not JSON, we'll process
                            'success': function (res) {
                                Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {
                                    res = Sys.Serialization.JavaScriptSerializer.deserialize(res);
                                    var data = res.d; //data is in d
                                    var list = $('<ul></ul>');

                                    for (var i = 0; i < data.length; i++) {
                                        list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));
                                    }
                                    $('#filtered-books').empty().html(list);
                                });
                            },
                            'error': function () {
                                alert('An error occurred.');
                            }
                        });
                    });

Again, I'm serializing the input parameters to the web service call using the ASP.NET Ajax serializer. We need to serialize in cases where we're using dates or of we have deep nested javascript objects. Since we're passing in the parameter as a simple string (i.e. the output of serialization), we need to tell WCF that that's actually a JSON string. For this, we need to use $.ajax and not $.post. With $.ajax, we get to specify the contentType as 'application/json'. Again, we're specifying the dataType as 'text' for the same reason as before. The rest should be self explanatory.

Here's the whole page code at a glance:

   
<!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></title>     
    <script src="/Scripts/MSAjax/Start.js" type="text/javascript"></script>     
    <script type="text/javascript" src="/Scripts/MSAjax/extended/ExtendedControls.js"></script>     
    <link href="/Scripts/MSAjax/extended/Calendar/Calendar.css" rel="stylesheet" type="text/css" />     
    <script type='text/javascript'> 
          Sys.loadScripts(['/Scripts/jquery-1.4.2.js'], function () {    
            $(function () {     
                $.post('/LibraryService.svc/ajax/GetAllBooks', null, function (res) {     
                    Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {     
                        res = Sys.Serialization.JavaScriptSerializer.deserialize(res);     
                        var data = res.d; //data is in d     
                        var list = $('<ul></ul>'); 
                          for (var i = 0; i < data.length; i++) {    
                            list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));     
                        }     
                        $('#all-books').empty().html(list);     
                    });     
                }, 'text');     
            }); 
              $(function () {    
                Sys.require([Sys.components.calendar], function () {     
                    var selectedDate = Date.parseInvariant('1-1-2000', 'd-M-yyyy');     
                    $('#txtDate').calendar({     
                        'format': 'dd-MM-yyyy',     
                        'selectedDate': selectedDate,     
                        'id': 'cal-txtDate'     
                    }); 
                      $('#btnFilter').click(function () {    
                        var date = $find('cal-txtDate').get_selectedDate();     
                        var params = { 'date': date} ;     
                        var serializedParams = Sys.Serialization.JavaScriptSerializer.serialize(params);     
                        $.ajax({     
                            'url': '/LibraryService.svc/ajax/GetBooksPublishedAfter',     
                            'data': serializedParams,     
                            'type': 'POST',     
                            'processData': false,     
                            'contentType': 'application/json',     
                            'timeout': 10000,     
                            'dataType': 'text', //not JSON, we'll process     
                            'success': function (res) {     
                                Sys.require([Sys.scripts.Serialization, Sys.scripts.Globalization], function () {     
                                    res = Sys.Serialization.JavaScriptSerializer.deserialize(res);     
                                    var data = res.d; //data is in d     
                                    var list = $('<ul></ul>');  
    
                                    for (var i = 0; i < data.length; i++) {     
                                        list.append('<li>' + data[i].Title + '- ' + data[i].Author + ', $' + data[i].Price.format('n') + ', ' + data[i].PublishDate.format('dd-MMM-yyyy'));     
                                    }     
                                    $('#filtered-books').empty().html(list);     
                                });     
                            },     
                            'error': function () {     
                                alert('An error occurred.');     
                            }     
                        });     
                    });     
                });     
            }); 
          });    
    </script>     
</head>     
<body>     
    <fieldset>     
        <legend>All Books</legend>     
        <div id='all-books'>     
        </div>     
    </fieldset>     
    <br />     
    <fieldset>     
        <legend>Filtered Books</legend>     
        <div>     
            Please select a date:     
            <input type='text' id='txtDate' /><br />     
            <input type='button' id='btnFilter' value='fetch books' />     
        </div>     
        <div id='filtered-books'>     
        </div>     
    </fieldset>     
</body>     
</html>    
    

Running the page, selecting a date and clicking the button should show something like this:

results4

 

Summary of Ajax Tests

Our Ajax-exposed WCF service is callable both from ASP.NET Ajax and jQuery. While jQuery is usually a lot leaner than ASP.NET Ajax in terms of things like animation and DOM manipulation, the new features of ASP.NET Ajax 4.0 make the latter a very attractive library for cleaner client template based databinding deriving its data from a service call. While jQuery also supports templates via plugins, ASP.NET Ajax 4.0's dataview control makes fetching data and displaying it in a reusable template quite elegant. The mere use of the word "elegant" relating to ASP.NET Ajax would have caused rounds of laughter in the past. v4.0 is looking quite good indeed.

Furthermore, WCF services handle dates in a specific way and also add some security to network calls. ASP.NET Ajax handles these issues seamlessly. Using jQuery, we had to pass and receive raw data and as such had to process the data before sending and after receiving. I used ASP.NET's serializer for this, although you could just as easily have used modified JSON2 (like Rick Strahl mentions in the previously mentioned link) or any other suitable technique. But the bottom line is that you would have had to handle these things which ASP.NET Ajax handles for you out of the box.

By this, I'm in no way saying that ASP.NET Ajax is better than jQuery or that jQuery is dead – far from it. What I'm saying is that there are things jQuery does way way better than ASP.NET Ajax, but handling WCF calls is not one of them. Those thinking about the size of the ASP.NET Ajax Library should rest assured that the on demand and in-parallel loading of only the required parts of the ASP.NET Ajax Library will ensure script downloads are very fast. Microsoft acknowledges that jQuery is a really really useful library and as such has adopted jQuery into the ASP.NET developer's toolbox. And with ASP.NET Ajax 4.0, it does seem like the two will finally complement each other.

Exposing to Silverlight

We'll now modify our configuration to support binary encoding for Silverlight. Please note that since we exposed our service's metadata for HTTP-GET via the "metadataGetBehavior" service behaviour, our service is already exposed to Silverlight. What we want to do now is expose the service for binary encoding. This will mean that data transfers are done in binary format, which is much more efficient than basic http. The first step in doing this is to add a custom binding. Right after our closing </behaviors> tag, add the following xml:

   
<bindings>     
      <customBinding>     
        <binding name="binaryEncoding">     
          <binaryMessageEncoding />     
          <httpTransport />     
        </binding>     
      </customBinding>     
    </bindings>    
    

The next step is to add a new endpoint for our service. Add the following to the <endpoints> section:

   
<endpoint address="binary"     
                  binding="customBinding" bindingConfiguration="binaryEncoding"     
                  contract="WCFEndpoints.LibraryService" />    
    

Notice that we're specifying "customBinding" for the binding and passing in "binaryEncoding" for the binding configuration. "binaryEncoding" is the binding configuration we added to the <customBinding> node in the <bindings> section.

And that's all it takes to enable binary encoding on a WCF sercive. No messy attributes or changing the C# code – just a few lines of configuration. Your WCF section of the web.config should now look like this:

   
<system.serviceModel>     
    <behaviors>     
      <endpointBehaviors>     
        <behavior name="AjaxBehavior">     
          <enableWebScript />     
        </behavior>     
      </endpointBehaviors>     
      <serviceBehaviors>     
        <behavior name="metadataGetBehavior">     
          <serviceMetadata httpGetEnabled="true"/>     
          <serviceDebug includeExceptionDetailInFaults="true"></serviceDebug>     
        </behavior>     
      </serviceBehaviors>     
    </behaviors>     
    <bindings>     
      <customBinding>     
        <binding name="binaryEncoding">     
          <binaryMessageEncoding />     
          <httpTransport />     
        </binding>     
      </customBinding>     
    </bindings>     
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"     
        multipleSiteBindingsEnabled="true" />     
    <services>     
      <service name="WCFEndpoints.LibraryService" behaviorConfiguration="metadataGetBehavior">     
        <endpoint address="ajax" behaviorConfiguration="AjaxBehavior"     
            binding="webHttpBinding" contract="WCFEndpoints.LibraryService" />     
        <endpoint address="binary"     
                  binding="customBinding" bindingConfiguration="binaryEncoding"     
                  contract="WCFEndpoints.LibraryService" />     
      </service>     
    </services>     
  </system.serviceModel>    
    

Silverlight Test

Let's create the Silverlight project. Add one named SilverlightWCFClient to the solution:

SL_Add

Keep the defaults for the popup that appears:

SL_Add2

Before we go any further, we'll want our main application to launch from the same port on the dev server. To do this, right click the WCFEndpoints project and select properties. From the Web tab, select the "Specific port" radio button, assign a port of your choice (that's free) and save.

port

Now right click the SilverlightWCFClient project in the solution explorer and select "Add Service Reference". Click on the "Discover" button. You should see something like this:

add_svc_ref

To ensure your metadata is discoverable, click on LibraryService.svc from the left pane and expand the tree. If everything's ok, you should get a tree on the left and a list of operations on the right. Remember that we added a "metadataGetBehavior"? That ensures that our service is discoverable by the Add Service Reference tool (and any other tools that consume wsdl). Name the service namespace "LibraryServiceReference" and click Ok.

add_svc_ref2

The tool generates some proxy classes, details of which can be seen in the object browser by double clicking the LibraryServiceReference:

svc_ref_object

It also adds a ServiceReferences.ClientConfig file:

   
<configuration>     
    <system.serviceModel>     
        <bindings>     
            <customBinding>     
                <binding name="CustomBinding_LibraryService">     
                    <binaryMessageEncoding />     
                    <httpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" />     
                </binding>     
            </customBinding>     
        </bindings>     
        <client>     
            <endpoint address="http://localhost:12171/LibraryService.svc/binary"     
                binding="customBinding" bindingConfiguration="CustomBinding_LibraryService"     
                contract="LibraryServiceReference.LibraryService" name="CustomBinding_LibraryService" />     
        </client>     
    </system.serviceModel>     
</configuration>    
    

Notice that the binding for the endpoint also has "binaryMessageEncoding" and "httpTransport". The tool was smart enough to see that our service had a binary endpoint and used that to ensure optimum data transfer efficiency. Also note the the endpoint address specifies the server and port number. It's for this reason that we explicitly specified a fixed port number for our web project.

I won't walk you through creating the Silverlight page that consumes the service. The code is pretty trivial and you can download the whole project's source from the link at the end of the article. After creating the page, if we launch SilverlightWCFClientTestPage.html (of the WCFEndpoints project) and click the button, we get the following output:

SL

Running the page in Firefox with Firebug's Net tab activated, we should see this (after clicking the button):

firebug1

Notice that the last two items are "POST binary". Expanding either one and selecting the Post tab, we get this:

firebug2

That shows us that the request data is being passed to the web service in binary format. Unfortunately, the version of firebug I'm using can't interpret the binary response, so the Response tab is pretty useless. Of course, had we been using basic http binding, we would have seen text data in the response, so our "useless" Response tab is actually suggesting that the response is not in a textual format.

Conclusion

In this article, we've seen how we can create a WCF service by coding once and expose it to different technologies merely by changing the configuration. We created the service and exposed it's metadata via Http GET. We had it expose to Ajax clients via configuration. We then created two plain html pages. In the first, we used ASP.NET Ajax 4.0's new features to consume the Ajax service quite elegantly. In the second, we used jQuery. In doing so, we saw how we needed to serialize / deserialize inputs / outputs to / from a WCF service when using jQuery. We used ASP.NET Ajax's serialization capabilities to do this. We then proceeded to add an endpoint to the WCF service that would allow communication in the more efficient binary format. Finally, we used firebug to show that the data transfer was indeed being done via binary and not basic http.

Source Code

Download

----------------------------------------------------------------------------------

Questions relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not. If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

----------------------------------------------------------------------------------