Consuming ASMX and WCF Services using jQuery
In the previous part I demonstrated how jQuery animations can add some jazz
to your web forms. Now let's see one of the most important feature of jQuery
that you will probably use in all data driven websites - accessing server data.
In the previous articles you used jQuery methods such as $.get() to make a GET
request to the server. More powerful feature, however, is to make AJAX calls to
ASP.NET Web Services, Page Methods and WCF services. The $.ajax() method of
jQuery allows you to access these services. In fact $.get() method you used
earlier internally makes use of $.ajax() method but restricts itself only to GET
requests. The $.ajax() method provides more control on how the
services are called.
The general signature of $.ajax() method is as follows :
$.ajax({
type: get_or_post,
url: url_goes_here,
data: service_parameters_goes_here,
contentType: "application/json; charset=utf-8",
dataType: json_or_xml,
processData: true_or_false;
success: function(results){...},
error: function(err) {...}
})
Though many of the parameters mentioned above (and there are still more!) are
optional knowing about them is a good idea so that you can have total control on
the AJAX calls being made. A brief description of each of the parameters
mentioned above is as follows:
- The type parameter indicates the type of request i.e. GET or POST
- The URL parameter is the URL to the remote service method to be called.
A typical URL will be MyService.asmx/MyMethod for Web Services and
MyService.svc/MyMethod for WCF services.
- The data parameter allows you pass parameters required by the remote
method. Typically data will be a JSON serialized object.
- contentType indicates the type of content being sent on the wire. Most
of the times it will be "application/json".
- dataType indicates whether data will be in JSON format or XML format.
- processData indicates whether to add data specified in data parameter to
request querystring. For POST requests you will set it to false.
- success is a function that will be invoked after successful completion
of the remote call.
- error is a function that will be invoked in case there is any error
while calling the remote service.
Example Services
As an example to illustrate how $.ajax() method can be used we will develop
two services and a couple of page methods. All of them essentially do a trivial
job - converting temperatures from Celsius to Fahrenheit and vice a versa. Our
web form will look like this:
The web form allows us to enter temperature value and its unit. Then we can
select whether to call ASMX or WCF service or Page Methods. Clicking on Convert
converts the temperature value to other unit and displays under Result column.
Creating ASMX Service
Create a new website and add a new Web Service (.asmx) to it. Then create the
following methods in the webservice.
[WebMethod]
public decimal ConvertTemperatureSimple(decimal t, string unit)
{
if (unit == "C")
{
t = (t * 1.8m) + 32;
}
else
{
t = (t - 32) / 1.8m;
}
return t;
}
[WebMethod]
public TemperatureData ConvertTemperatureComplex(TemperatureData t)
{
if (t.Unit == "C")
{
t.Value = (t.Value * 1.8m) + 32;
t.Unit = "F";
}
else
{
t.Value = (t.Value - 32) / 1.8m;
t.Unit = "C";
}
return t;
}
The ConvertTemperatureSimple() method has parameters and return values as
simple primitive data types. It accepts temperature value to convert and its
current unit. Internally depending on whether the unit is "C" or "F" it converts
the temperature to another unit. The calculated temperature is then returned.
The ConvertTemperatureComplex() method makes use of complex data types i.e.
TemperatureData class. Internal working of ConvertTemperatureComplex() method is
similar to the previous method. The TemperatureData class is shown below:
public class TemperatureData
{
public TemperatureData() { }
public TemperatureData(decimal value, string unit, DateTime takenon)
{
this.Value = value;
this.Unit = unit;
this.TakenOn = takenon;
}
public decimal Value { get; set; }
public string Unit { get; set; }
public DateTime TakenOn { get; set; }
}
The TemperatureData class essentially stores temperature value, unit and date
on which the temperature is recorded (though we don't use this property for any
meaningful purpose in this example).
Creating WCF Service
We will also add a WCF service to our website that is equivalent in terms of
functionality to the web service we just developed. The following code shows WCF
service methods.
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped)]
decimal ConvertTemperatureSimple(decimal t,string unit);
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
TemperatureData ConvertTemperatureComplex(TemperatureData t);
}
[DataContract]
public class TemperatureData
{
public TemperatureData() { }
public TemperatureData(decimal value, string unit,DateTime takenon)
{
this.Value = value;
this.Unit=unit;
this.TakenOn = takenon;
}
[DataMember]
public decimal Value { get; set; }
[DataMember]
public string Unit { get; set; }
[DataMember]
public DateTime TakenOn { get; set; }
}
public class Service : IService
{
public decimal ConvertTemperatureSimple(decimal t, string unit)
{
if (unit == "C")
{
t = (t * 1.8m) + 32;
}
else
{
t = (t - 32)/1.8m;
}
return t;
}
public TemperatureData ConvertTemperatureComplex(TemperatureData t)
{
if (t.Unit == "C")
{
t.Value = (t.Value * 1.8m) + 32;
t.Unit = "F";
}
else
{
t.Value = (t.Value - 32) / 1.8m;
t.Unit = "C";
}
return t;
}
}
I won't go into the technical details of creating a WCF service here. You
will find many articles on this website that teach you how do that. Notice the
lines marked in bold letters. That's an important piece of code for us. The [WebInvoke]
attribute from System.ServiceModel.Web namespace marks a service method as REST
callable. REST (REpresentational State Transfer) is a programming model for
getting information from a web accessible URL (typically to a web page or
service). In jargon free terms REST means retrieving data from a web accessible
resource (e.g. a web page) using HTTP GET or POST methods.
The WebInvoke attribute has several properties as described below:
- Method : Indicates HTTP verb GET or POST that can invoke this method.
- RequestFormat and ResponseFormat : Indicate the format of request and
response respectively. Possible values are JSON and XML.
- BodyStyle : Indicates whether to wrap data within system defined XML
elements or not. Possible options are Bare, Wrapped, WrappedRequest and
WrappedResponse. For methods with only one parameter the default of Bare
works fine. However, if your service method is taking more than one
parameters then you should make it Wrapped.
Creating Page Methods
Creating Page Methods is very similar to creating Web Service methods. The
only difference is that they reside in the code behind file of the web form and
are static. Have a look below:
[WebMethod]
public static decimal ConvertTemperatureSimple(decimal t, string unit)
{
...
}
[WebMethod]
public static TemperatureData ConvertTemperatureComplex(TemperatureData t)
{
...
}
Calling Service Methods
The jQuery code that calls the service methods is inside the click event
handler of Convert button. The event handler is wired in the ready event as
shown below:
$(document).ready(function() {
$("#btnConvert").click(OnConvert);
});
The OnConvert function that acts as the click event handler of Convert button
contains switch-case statement that checks the value selected in dropdown
Select2.
function OnConvert(event) {
var url = "";
var data="";
var successHandler=null;
var temp = $("#TextBox1").val();
var unit=$("#Select1").val();
switch ($("#Select2").val()) {
case "ASMX1":
url = "WebService.asmx/ConvertTemperatureSimple";
data = '{"t":"' + temp + '","unit":"' + unit + '"}';
successHandler=function(results) {
$("#lblResult").text(results.d);
$("#lblTakenOn").text("");
}
break;
case "ASMX2":
url = "WebService.asmx/ConvertTemperatureComplex";
data = '{t:{"Value":"' + temp + '","Unit":"' + unit +
'","TakenOn":"12/10/2010"}}';
successHandler = function(results) {
$("#lblResult").text(results.d.Value + " " + results.d.Unit);
$("#lblTakenOn").text("on " + ToJSDate(results.d.TakenOn));
}
break;
case "WCF1":
url = "Service.svc/ConvertTemperatureSimple";
data = '{"t":"' + temp + '","unit":"' + unit + '"}';
successHandler=function(results) {
$("#lblResult").text(results.ConvertTemperatureSimpleResult);
$("#lblTakenOn").text("");
}
break;
case "WCF2":
url = "Service.svc/ConvertTemperatureComplex";
data = '{"Value":"' + temp + '","Unit":"' + unit + '","TakenOn":"' +
ToWCFDate("12/10/1960") + '"}';
successHandler=function(results) {
$("#lblResult").text(results.Value + " " + results.Unit);
$("#lblTakenOn").text("on " + ToJSDate(results.TakenOn));
}
break;
case "PM1":
url = "Default.aspx/ConvertTemperatureSimple";
data = '{"t":"' + temp + '","unit":"' + unit + '"}';
successHandler = function(results) {
$("#lblResult").text(results.d);
$("#lblTakenOn").text("");
}
break;
case "PM2":
url = "Default.aspx/ConvertTemperatureComplex";
data = '{t:{"Value":"' + temp + '","Unit":"' + unit +
'","TakenOn":"12/10/2010"}}';
successHandler = function(results) {
$("#lblResult").text(results.d.Value + " " + results.d.Unit);
$("#lblTakenOn").text("on " + ToJSDate(results.d.TakenOn));
}
break;
}
$.ajax({
type: "POST",
url: url,
data: data,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: successHandler,
error: function(err) {
alert(err.status + " - " + err.statusText);
}
})
}
Each "case" essentially forms the REST endpoint to be invoked. As described
earlier the general format of URL is
<service_asmx_or_svc_file_name>/<method_name>
The method parameters need to be in JSON format. JSON format is basically a
key-value format wherein keys and values are enclosed in double quotes and are
separated by :. Multiple key-value pairs are separated by a comma (,). The
entire set of key-value pairs is enclosed within { and }. For primitive data
types key name must be same as the method parameter name and for object types
key name must match the property name. Notice the differences between the JSON
strings for ASMX and WCF.
The success handler receives the return value of the service method as a
parameter (result). For Web Services the return value is made available as a
member of implicit object d. For WCF services you can access the return value
directly.
JSON format takes care of serializing data types such as string, integers and
boolean. However, there is no way to represent Date data type in JSON. That
means dates are treated as plain strings. To avoid any confusion ASMX and WCF
services serialize dates in a special format. The format is as follows:
\/Date(no_of_mlilliseconds_since_1_Jan_1970)\/
To take care of this format we will create two helper methods ToWCFDate() and
ToJSDate().
function ToJSDate(value) {
var pattern = /Date\(([^)]+)\)/;
var results = pattern.exec(value);
var dt = new Date(parseFloat(results[1]));
return (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear();
}
function ToWCFDate(value) {
var dtArr = value.split("/");
var dt = new Date(dtArr[2], --dtArr[0], dtArr[1]);
var date = '\/Date(' + dt.getTime() + '+0000)\/';
return date;
}
The ToJSDate() function accepts date in the special format as mentioned above
and then converts it into mm/dd/yyyy format. It does so by first matching a
regular expression and then converting the millisecond value back into a
JavaScript date. Similarly ToWCFDate() function converts specified date from mm/dd/yyyy
format to WCF service specific format using getTime() function. The getTime()
function returns number of milliseconds since 1 January 1970 and that specific
date instance. The time zone information is also appended (+0000 or whatever
time zone you want say +0530 for IST)
That's it! Run the web form and try converting a few temperature values using
ASMX and WCF service methods.