Attachment: JSON-Enabled WCF Services (part 2).zip
This article is to continue the article JSON WCF Service (part 1), in which we created WCF service taking and giving out data in JSON format.
In this article, we are going to implement 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 us to write code in C#, programming language we got used to and translate it to JavaScript.
You can get some basic knowledge about Script# from the article Script# insights 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 start working.
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 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 address to the language specifications.
It can be done in multiple ways. I will show you all of them, so you can choose one that is the most convenient for you.
The first one and, probably, the easiest way is to create data model specially for Script# projects.
For example, message class ContinentPopulation for common C# projects looks as follows:
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 Script# it will be:
{
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 sample application that contains only few classes of 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 to use preprocessor directives.
First of all we need to add conditional compilation symbol to data model service project Let’s call it SERVICEMODEL. We will use it in the #IF directive in order to define server side code which will not be used in Script# classes.
Add a project to our solution that will contain data model for Script#. Name it ClientDataModel.
As you add the project, you will be suggested to set the folder to copy the generated js files to. I set Scripts folder of the web application.
It is necessary to add references to data model service classes in the created projects.
Let’s see how 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. I will just consider code of the ContinentPopulation class. Changes for the rest of data model classes will be the same.
Code of the ContinentPopulation class is shown below:
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. }
The fragment containing mixed client and server side code will look like:
#else
#endif
Now let’s consider the listing above.
Our class doesn’t use System.Text namespace, that is why line 4 may be deleted. System.Runtime.Serialization namespace doesn’t exist in Script#, but it is used in C# (contains DataContract and DataMemeber classes), that is why line 5 should be replaced by the following code:
using System.Runtime.Serialization;
#endif
We don’t need 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 properties. Code of the changed class is shown below:
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 in this way. 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 ServiceDataModel to DataModel
Let’s proceed to implementation of the client side of our application.
Add “jQuery Script Library” project under “ClientLibrary” name to our solution.
In order to avoid copying js files to web application during assembling Script# project you can set Scripts folder in the way we did it when adding Script# project to 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 got used to (based on the specification ISO-2 of C#).
I can schematically describe interaction of WCF service and client application in the following way.
WCF has 2 public methods: GetContinents and GetContinentDetails that are defined by IContinentsPopulation interface.
In the client application, it is possible to define service layer that will do all the job of getting data from the service – interact with service methods GetContinents and GetContinentDetails. Define IService interface with methods: GetContinents and GetContinentDetails which names are analogous to the names of 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. Interface code is shown below.
{
void GetContinents(AjaxRequestCallback successCallback, AjaxErrorCallback errorCallback);
void GetContinentDetails(ContinentDetailRequest request, AjaxRequestCallback successCallback, AjaxErrorCallback errorCallback);
}
Let’s create class implementing IService interface, name it ServiceProvider. 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 consider it. Let’s consider GetRequestData and 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:
In this case an object of the ContinentDetailRequest type with 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 ContinentDetailRequest type with “request” key.
Code of this method will look as follows after conversion to JavaScript:
var dataRequest = {};
dataRequest['request'] = obj;
return dataRequest;
...
It can be done in the other way. Create class that will contain field with request name and object type.
For example, RequestData class as shown below:
{
public object request;
}
Then it will be possible to create an object of RequestData type in the PrepareDataForRequest method, set field value to request and pass it for serialization:
{
RequestData t = new RequestData();
t.request = obj;
return t;
}
Add SetData method to IService interface. This method will process data came from the server deleting unnecessary information. And correspondingly implementation of this method in the ServiceProvider class will look like this:
{
return JsonDataConverter.GetData(serverObject);
}
Client application will contain three layers – one layer has been considered already – this is the layer of interaction with server. Next layer is layer of data processing, it will prepare data necessary for execution of methods on the server and handle response from the server. The third layer is responsible for displaying data on the page.
Now let’s add to the ClietLibrary project a class that will manage the process of getting data from the server (data processing layer). Name it ContinentsManager.
We won’t consider code of this class as it quite simple for understanding. I will just mention that this class should somehow notify data display layer that the data has been loaded. JavaScript doesn’t have 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:
public event ContinentsListLoadedCallback ContinentsListLoaded;
Every event type should have a delegate. Corresponding delegates are provided below:
public delegate void ContinentDetailsLoadedCallback(ContinentDetails continentDetails);
Let’s proceed to the data display layer.
I’ll show you how the page with data should look like:
Table structure is as follows:
<table id="tbt_t">
<thead>
<tr>
<th> Name</th>
<th> Population</th>
</tr>
</thead>
<tbody>
<tr>
<td>[name]</td>
<td>[population]</td>
</tr>
</tbody>
</table>
Add ViewManager class to the ClientLibrary project. This class will be responsible for data displaying. On the previous step, we created ContinentsManager class that allows getting of data from the server. It has 2 asynchronous methods and corresponding events. Let’s create a private field of the ContinentsManager type with name _continentsManager. Initialize this field in the ViewMethod constructor.
{
_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 will setup and configure element for data diaply. And LoadData method initializing data upload from the server. Let’s review work of the SetElement method. It calls InitializeViewElement method which code you can see below:
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 page element. This line looks like this in JavaScript:
The second line checks if the elements are found. The fourth line gets the first element – JavaScript equivalent
The fifth line sets CSS property “border” for our element by using JQuery – JavaScript equivalent:
Code of the InitializeHeader method is set in the table below where each line of code in Script# corresponds to a line of code in JavaScript. This method create table caption with 2 columns “Name” and “Population”.
Script#
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
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);
_continentsManager_ContinentsListLoaded method is called when a list containing continents and popularity was loaded from the server. _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 click event. This event handler gets cell content – continent name – and queries detailed data for this continent.
"click",
delegate(ElementEvent e)
{
_continentsManager.GetContinentDetails(e.Target.InnerText);
},
false);
_continentsManager_ContinentDetailsLoaded method is called when detailed data on continent is loaded from the server. This method forms strings with continent data and displays it in the modal window.
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());
ViewManager class is ready, and correspondingly 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:
<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>
Element we will use for data display:
Add the following JavaScript code:
$(document).ready(function () {
viewManager.setElement("#dataTable");
viewManager.loadData();
});
This code creates object of 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 countires with population. If you click on the country name detailed data on this country will be loaded from the server and displayed in the dialog window of the browser.
Hi Sergey, nice write-up. Two questions though:
1) I’m not persuaded that conditional compilation for message classes is the best solution. Wouldn’t the autogeneration (T4 etc) fit better?
2) Would be interesting to compare the “pure javascript” solution to the Script# one. I wonder what are the biggest benefits of Script# not counting the lack of need to learn javascript (because in this case Script# output becomes a leaky abstraction, which I deem is not good).
Sergey, thanks for the write up, it’s very nicely done and the approach might just work for my project as well.
Andrew: the abstraction may be leaky, but from my perspective Script# has fairly pragmatic benefits of eliminating a few classes of bugs related to type safety (including function call signatures), and then also allowing to implement class hierarchies more reliably.
Hello, Andrew!
1) Yes, it is possible that this solution is not the best one for creation of message classes.
The use of Text Template Transformation Toolkit allows us to reduce the code which a developer will write for each message class.
It is also allows to save time on support for classes correspondence among the message classes in C# to the message classes in Script#.
2) The use of Script# allows the use of the code on Javascript by the developers who don’t know Javascript in their projects.
If you use Script# on Javascript, you get more pure code. It is recommended to use ISO-2 as C# specification.
I didn’t compared the code in Script# and generated code in Javascript (I mean large code) to the pure Javascript code.
But comparing not large functions, I noticed that the code became smaller than the code written in pure Javascript.