Click here to Skip to main content
15,867,939 members
Articles / Programming Languages / JScript .NET

JSON-Enabled WCF Services (Part II)

Rate me:
Please Sign up or sign in to vote.
2.83/5 (3 votes)
26 Jan 2012CPOL9 min read 35.7K   508   17   3
Implementing a JavaScript-based client side to interact with a WCF service

Introduction

This article continues the JSON WCF Service (part 1) article where we described how to create a WCF service that receives and passes data in the JSON format.

And now we are going to implement a JavaScript-based client side that will interact with our WCF service. However, we won’t write a single line of pure JavaScript code in order to do this. Instead, we will use Script#. It allows writing code in C#, programming language we got used to and then convert it to JavaScript.

You can get some basic knowledge about Script# from the Script# insights article or on the official website of the product [http://projects.nikhilk.net/ScriptSharp]. There you can download the latest version of Script#.

So, let’s get started!

Creating Client Data Model Classes

We start from the message classes: ContinentPopulation, ContinentDetails, ContinentDetailRequest, ContinentDetailsResponse, ContinentsListResponse.

As you remember, members of every class above are automatically implemented properties. However, if you try to assemble a Script# project with such properties, you will get the following error: “Feature ‘automatically implemented properties’ cannot be used because it is not part of the ISO-2 C# language specification”. So we have to modify our classes so that they meet the language specifications.

It can be done in several ways. I will show you all of them, so you can choose the one that is the most convenient for you.

The first one and, probably, the easiest way is to create the data model specially for Script# projects. For example, the ContinentPopulation message class for a common C# projects looks as follows:

C#
[DataContract(Name = "ContinentPopulation")]
public class ContinentPopulation
{
[DataMember(Name = "ContinentName",IsRequired = true,Order = 0)]
public string ContinentName { get; set; }

[DataMember(Name = "TotalPopulation", IsRequired = true, Order = 1)]
public int TotalPopulation { get; set; }
}

In the Script# language, it will be like follows:

C#
   public class ContinentPopulation
{
public string ContinentName;
public int TotalPopulation;
}

This is not the best way, and I wouldn’t recommend using it. Yes, it can be good if you create a sample application that contains just few classes of the data model. But real projects include huge amount of data model classes and may reach up to hundred classes. Just imagine that you will need to continuously synchronize changes in these classes.

In order to avoid duplication of data model classes, I suggest using preprocessor directives. First of all, we need to add a conditional compilation symbol to a data model service project. Let’s call it the SERVICEMODEL. We will use it in the #IF directive to define server side code which will not be used in Script# classes.

Image 1

Creating a Data Model Project

Now we add a project to our solution that will contain the data model for Script#. Let’s name it as the ClientDataModel.

Image 2

When adding the project, you will be suggested to specify a folder to copy the generated js files: let’s choose the Scripts folder of the web application.

Image 3

Now it is necessary to add references to the data model service classes in the created projects.

Image 4

Let’s see how the class code should look like. I won’t show code of each class, you will be able to view it in the sample attached to this article. Let’s see just the code of the ContinentPopulation class. Changes for the rest of data model classes will be the same.

C#
1.  using System;
2.  using System.Collections.Generic;
3.  using System.Linq;
4.  using System.Text;
5.  using System.Runtime.Serialization;
6.
7.  namespace DataModel
8.  {
9.      [DataContract(Name = "ContinentPopulation")]
10.     public class ContinentPopulation
11.     {
12.         [DataMember(Name = "ContinentName", IsRequired = true, Order = 0)]
13.         public string ContinentName
14.         {
15.             get;
16.             set;
17.         }
18.
19.         [DataMember(Name = "TotalPopulation", IsRequired = true, Order = 1)]
20.         public long TotalPopulation
21.         {
22.             get;
23.             set;
24.         }
25.     }
26. }

And here is a fragment that contains mixed client and the server side code:

C#
#if SERVICEMODEL

#else

#endif

Now let’s review the listing above. Our class doesn’t use the System.Text namespace, that is why line 4 may be deleted. The System.Runtime.Serialization namespace doesn’t exist in Script#, but it is used in C# (contains the DataContract and DataMember classes), that is why line 5 should be replaced by the following code:

C#
#if SERVICEMODEL
using System.Runtime.Serialization;
#endif

We don’t need the DataContract and DataMember attributes in Script#, that is why we will place them in the directive “#if SERVICEMODEL” (lines 9, 12, 19).

We’ll do the same with the properties. You can see the code of the changed class below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
#if SERVICEMODEL
using System.Runtime.Serialization;
#endif

namespace DataModel
{
#if SERVICEMODEL
[DataContract(Name = "ContinentPopulation")]
#endif
public class ContinentPopulation
{
#if SERVICEMODEL
[DataMember(Name = "ContinentName", IsRequired = true, Order = 0)]
#else
[PreserveCase()]
#endif
public string ContinentName
#if SERVICEMODEL
{
get;
set;
}
#else
;
#endif
#if SERVICEMODEL
[DataMember(Name = "TotalPopulation", IsRequired = true, Order = 1)]
#else
[PreserveCase()]
#endif
public long TotalPopulation
#if SERVICEMODEL
{
get;
set;
}
#else
;
#endif
}
}

All data model classes should be transformed this way. The PreserveCase attribute is used in order to avoid conversion of the class member name when generating JavaScript code. Finally, we will change namespace in classes of our data model from the ServiceDataModel to the DataModel. And now let’s proceed with the implementation of the client side of our application.

Creating a Client Script# Application

We add the “jQuery Script Library” project under the “ClientLibrary” name to our solution.

Image 5

To avoid copying the js files to web application during assembling of the Script# project, you can set the Scripts folder in the way we did it when adding the Script# project to the data model. Let’s forget that JavaScript has no idea about interface or class, let’s forget about JavaScript at all. Let’s write code in C# in the way we used to (based on the specification ISO-2 of C#).

Service Interaction Layer

I can schematically describe the interaction of a WCF service and a client application the following way.

WCF has two public methods: the GetContinents and the GetContinentDetails which are defined by the IContinentsPopulation interface.

In a client application, it is possible to define a service layer that will do all the job of getting data from the service – interact service methods: the GetContinents and the GetContinentDetails. We define the IService interface with the following methods: GetContinents and GetContinentDetails, and their names are analogous to the names of the service methods.

Interaction with the service will be asynchronous, that is why interface methods will transmit callback methods. They will be executed after successful or failed execution of the query. Code for the interface is shown below.

C#
public interface IService
{
void GetContinents(AjaxRequestCallback successCallback, AjaxErrorCallback errorCallback);

void GetContinentDetails(ContinentDetailRequest request, 
    AjaxRequestCallback successCallback, AjaxErrorCallback errorCallback);
}

Let’s create a class implementing the IService interface, name it as ServiceProvider. The ServiceProvider class will form AJAX queries to the server, set handlers for successful or failed completion of the query. Implementation of the class is quite simple that is why we won’t review it here. Let’s better review the GetRequestData and the PrepareDataForRequest methods.

GetRequestData method executes conversion of the object to a string in JSON format by using the standard class provided by Script#.

GetContinentDetails method on WCF service has argument with the “request” name. It slightly limits query data, it should be provided in the following way:

C#
{ "request" : { "ContinentName" : "" } }

In this case, an object of the ContinentDetailRequest type with the request name will be created when serializing data on WCF service. That is why we create a dictionary in the Script# project and place all objects of the ContinentDetailRequest type with the “request” key. The code of this method will look as follows after converting it to JavaScript:

C#
...
var dataRequest = {};
dataRequest['request'] = obj;
return dataRequest;
...

It can be done other way: by creating a class that will contain a field with the request name and object type. For example, the RequestData class as shown below:

C#
public class RequestData
{
public object request;
}

Then it will be possible to create an object of the RequestData type in the PrepareDataForRequest method, set a field value to request and pass it for serialization:

C#
private object PrepareDataForRequest(object obj)
{
RequestData t = new RequestData();
t.request = obj;
return t;
}

Add the SetData method to the IService interface. This method will process data which are passed from server deleting unnecessary information. And correspondingly the implementation of this method in the ServiceProvider class will look as follows:

C#
public object GetData(object serverObject)
{
return JsonDataConverter.GetData(serverObject);
}

A client side of an application will contain three layers – one layer has been reviewed already – this is the layer of interaction with server. Next layer is a layer of data processing. It prepares data required for execution of methods on the server side and handles response from the server. The third layer is responsible for displaying data on the page.

Now let’s add a class that will manage the process of getting data from the server (data processing layer) to the ClientLibrary project and give it the ContinentsManager name.

We won’t consider code of this class as it quite simple for understanding. It is just worth mentioning that this class should somehow notify the data display layer that the data has been loaded. JavaScript doesn’t have an event, but Script# allows us to describe events in the way we do it in C# hiding from us the whole implementation of the mechanism. That is why we can certainly describe the following events in the ContinentsManager class:

C#
public event ContinentDetailsLoadedCallback ContinentDetailsLoaded;
public event ContinentsListLoadedCallback ContinentsListLoaded;
Every event type should have a delegate. Corresponding delegates are provided below:
public delegate void ContinentsListLoadedCallback(List continents);
public delegate void ContinentDetailsLoadedCallback(ContinentDetails continentDetails);

And now let’s proceed to the data processing and displaying layer.

Data Processing and Display Layer

Let me show you how a page with data should look like:

Image 6

The table structure is as follows:

HTML
<table id="tbt_t">
<thead>
<tr></tr>
<th>
 Name</th>
<th>
 Population</th>
</tr>
</thead>
<tbody></tbody>
<tr></tr>
<td></td>
[name]</td>
<td></td>
[population]</td>
</tr>
</tbody>
</table>

Now we add the ViewManager class to the ClientLibrary project. This class is responsible for data displaying. During the previous step, we created the ContinentsManager class that allows getting data from the server. It has two asynchronous methods and the corresponding events. Let’s create a private field of the ContinentsManager type with the _continentsManager name. Initialize this field in the ViewMethod constructor.

C#
public ViewManager()
{
_continentsManager = new ContinentsManager();
_continentsManager.ContinentDetailsLoaded += 
    new ContinentDetailsLoadedCallback(_continentsManager_ContinentDetailsLoaded);
_continentsManager.ContinentsListLoaded += 
    new ContinentsListLoadedCallback(_continentsManager_ContinentsListLoaded);
}

Add the SetElement method to the ViewManager class. This class sets up and configures element for data display. Let’s review work of the SetElement method. It calls the InitializeViewElement method (you can see its code below):

C#
1.  jQueryObject viewObject = jQuery.Select(selector);
2.  if (viewObject.Length > 0)
3.  {
4.      _viewElement = (TableElement)viewObject.GetElement(0);
5.      jQuery.FromElement(_viewElement).CSS("border", "1px solid black");
6.      InitializeHeader();
7.  }

In the first code line, we get the page element. This line looks like this in JavaScript:

C#
var viewObject = $(selector);

The second line checks if the elements are found. The fourth line gets the first element – the JavaScript equivalent:

C#
this._viewElement = viewObject.get(0);

The fifth line sets the CSS property “border” for our element using JQuery – the JavaScript equivalent:

CSS
$(this._viewElement).css('border', '1px solid black');

The code of the InitializeHeader method is set in the table below where each line of code in Script# corresponds to a line of the code in JavaScript. This method creates a table caption with 2 columns: “Name” and “Population”.

Script#

C#
Element headElement = Document.CreateElement("thead");
Element trElement = Document.CreateElement("tr");
Element thNameElement = Document.CreateElement("th");
jQuery.FromElement(thNameElement).Text("Name");
Element thPopulationElement = Document.CreateElement("th");
jQuery.FromElement(thPopulationElement).Text("Population");
trElement.AppendChild(thNameElement);
trElement.AppendChild(thPopulationElement);
headElement.AppendChild(trElement);
_viewElement.AppendChild(headElement);

JavaScript

C#
var headElement = document.createElement('thead');
var trElement = document.createElement('tr');
var thNameElement = document.createElement('th');
$(thNameElement).text('Name');
var thPopulationElement = document.createElement('th');
$(thPopulationElement).text('Population');
trElement.appendChild(thNameElement);
trElement.appendChild(thPopulationElement);
headElement.appendChild(trElement);
this._viewElement.appendChild(headElement);

The _continentsManager_ContinentsListLoaded method is called when a list containing continents and popularity is loaded from the server. The _continentsManager_ContinentsListLoaded method searches the list of clients and adds data to the table. When the table row is formed, a cell containing data about continent name is bound to the click event. This event handler gets cell content – continent name in our case – and queries detailed data for this continent.

C#
tdNameElement.AddEventListener(
"click",
delegate(ElementEvent e)
{
_continentsManager.GetContinentDetails(e.Target.InnerText);
},
false);

The _continentsManager_ContinentDetailsLoaded method is called when the detailed data on continent is loaded from the server. This method forms strings with continent data and displays them in a modal window.

C#
StringBuilder builder = new StringBuilder();
builder.AppendLine(string.Format("ContinentName: {0}", continentDetails.ContinentName));
builder.AppendLine(string.Format("Area: {0}", continentDetails.Area));
builder.AppendLine(string.Format("PercentOfTotalLandmass: {0}", 
    continentDetails.PercentOfTotalLandmass));
builder.AppendLine(string.Format("TotalPopulation: {0}", 
    continentDetails.TotalPopulation));
builder.AppendLine(string.Format("PercentOfTotalPopulation: {0}", 
    continentDetails.PercentOfTotalPopulation));
Script.Alert(builder.ToString());

The ViewManager class is ready, and correspondingly the data display layer is ready as well.

Let’s see how to use all we created in Script# by adding all necessary scripts to the page:

HTML
<script src="Scripts/jquery-1.6.2.js" type="text/javascript"></script>
<script src="Scripts/mscorlib.debug.js" type="text/javascript"></script>
<script src="Scripts/ClientDataModel.debug.js" type="text/javascript"></script>
<script src="Scripts/ClientLibrary.debug.js" type="text/javascript"></script>

And add the following JavaScript code:

JavaScript
var viewManager = new ClientLibrary.ViewManager();
$(document).ready(function () {
viewManager.setElement("#dataTable");
viewManager.loadData();
});

This code creates an object of the ClientLibrary.ViewManager class. Set an element in the page loaded event handler, this element will be used to display data and call data upload method.

The application looks in the browser as shown in the picture below. Table displays a list of countries with population. If you click the country name, detailed data on this country will be loaded from the server and displayed in the dialog window of the browser.

Image 7

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Perpetuum Software LLC
Russian Federation Russian Federation
I am a senior software developer in Perpetuum Software and specialize on the development and support of software components for programmers.
My technology expertise includes: .Net Framework (Windows forms, ASP.NET, Silverlight, WCF), HTML5, JavaScript.

Comments and Discussions

 
Questiongreat solution but how to host in IIS Pin
robertclancy5-Nov-12 15:05
robertclancy5-Nov-12 15:05 
Question[My vote of 2] errros from source files Pin
Donsw24-Jul-12 16:10
Donsw24-Jul-12 16:10 
the two projects clientdatamodel and clientlibrary do not load. the missing files are scriptsharp\v1.0\scriptsharp.targets. not sure where these come from. but missing files are important to see how the program actually works. D'Oh! | :doh:
cheers,
Donsw
My Recent Article : CDC - Change Data Capture

QuestionError Running Latest Source Code Pin
GCLee8-Mar-12 7:37
GCLee8-Mar-12 7:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.