Tuesday, April 20, 2010

Calling web service using javascript. Part - 2

In the first part of the blog we saw how to invoke web methods using javascript. In this blog we will take a look at how to invoke web service methods using Webservice behavior (HTC file). WebService behavior is a component which helps in invoking web service methods using SOAP protocol. The HTC file can be freely downloaded from Microsoft’ site. By default HTC file support asynchronous invocation of web service methods. One can enable synchronous way of invocation as well, but do keep in mind that synchronous invocation can freeze the screen (browser) till the web method execution is completed, which may not be desired in web application.

Making use of WebService behavior (HTC file)

To make use of WebService behavior to invoke web service methods we need to download webservice.htc file from here. Once you have downloaded the HTC file save it in any folder in your website. Once the “webservice.htc” file has been downloaded and saved in your application the next thing is to hook up the “htc” file to any of the html control using the style attribute. Once you have attached the “htc” file to a control then you can use the various methods in the “htc” file to invoke web service methods. Sample javascript code and web service is pasted below.

<body>
<script language="javascript" type="text/javascript">
function callWebService()
{
     var webSer = document.getElementById("webServiceHTC");
     webSer.useService(“http://localhost:3150/TestService.asmx?wsdl”, "ServiceTest");
     var uniqueID = webSer.ServiceTest.callService(handleResult, "HelloWorld");       
}
function handleResult(result)
{
    alert(result.value);
}
</script>
  <form id="form1" runat="server">
    <input type=”text” id="webServiceHTC" style="behavior:url(htc/webservice.htc)"/>
    <asp:Button ID="javascripWSInvoke" runat="server" Text="Invoke" OnClientClick="javascript:callWebService(); return false;" />
  </form>
</body>

//Web service method

[WebMethod]
public string HelloWorld()
{
    return "Hello World";
}

In the code pasted above, we are attaching the “webservice.htc” file using the html “style” property of an html control. Once the “webservice.htc” file is attached to a html control you can access the “htc” file by retrieving the control using the “getElementById” javascript method. As you can see, from the above code, we are retrieving the textbox control using the “getElementById” method and we are making use of the “useService” method defined in the “webservice.htc” file. Using the “useService” method one can register a web service and give it a friendly name. The method takes two arguments, first is the url of the web service and the second is the friendly name. The friendly name is then used along with the control to invoke web service methods, as pasted in the above code, where we are using the friendly name along with “callService” method to invoke web method. “callService” method has optional first argument which is the name of the javascript function which will handle the result returned from the web method, second argument is the name of the web method which needs to be invoked and then list of parameters separated by comma.

“handleResult” method handles the result returned from the web service method. The method takes an argument which will hold the value returned from the web service method. In the “handleResult” we are showing the output in a alert message. The result argument will have four properties namely “error”, “id”, “raw” and “value”. “error” is a boolean value which indicates whether there was any error, “id” holds the unique id returned while invoking the web method, “raw” will have the raw XML and “value” will have the actual value returned from the web service method. “value” property can have simple as well as complex objects.

Passing parameters to a web method.

The above sample was a simple web method invocation without parameters. Now, lets see how to pass parameters to a web method.

function passParameter()
{
        //Getting the htc file attached object.
        var txtCntl = document.getElementById("divHTC");
        //Assigning the web service url.
        txtCntl.useService("http://localhost:3150/TestService.asmx?wsdl", "webServ");
        //Variables to pass as arguments to the web method.
        var fName = "Sandeep";
        var lName = "P R";
        var mName = "";
        //Invoking the "CombineName" web method.
        var uniqueID = txtCntl.webServ.callService(handleResult, "CombineName", fName, mName, lName);
}

The above code is pretty much the same as the one we have previously used to invoke a web method. The only difference here is that we are passing arguments as a comma separated list to “callService” method. As explained before the first two arguments of “callService” method take the name of the javascript function which handles the response and second argument is the name of the web method to be invoked. The next set of arguments starting from the third arguments are the parameters which needs to be passed to the web method. You can pass any number of arguments separated by comma. Once the web method returns the output the control is passed to “handleResult” javascript method which does nothing other than elegantly showing output in a alert box.

Lets see the code for the web method invoked in the above javascript method.

[WebMethod]
public string CombineName(string firstName, string midleName, string lastName)
{
        return string.Concat(string.Concat(firstName, " - ", midleName), " - ", lastName);
}

The above web method is pretty straight forward. It concatenates all the arguments passed into it and returns it as a single string.

Sending complex objects to a web method

We have invoked web method without parameter, with parameters and next is what? Obviously, passing complex objects to a web service method. It is very much possible to pass complex objects to a web method using web service behavior. Below seeing the javascript code in action lets see the code for the web service method and complex object which forms the method argument.

//Web service method which takes complex object "Car" as an argument.
[WebMethod]
public string GetCar(Car car)
{
     return "Model: " + car.Model + ", Cubic capacity: " + car.CC + ", Color: " + car.Color;
}

//The "Car" class.
public class Car
{
    public string Model
    {
         get;
         set;
    }

    public int CC
    {
         get;
         set;
    }

    public string Color
    {
         get;
         set;
    }
}

The above web method is pretty straight forward, it takes a complex object (Car) as one of its argument and concatenates all the properties of the “Car” class and returns it as a string. Now lets see how to pass the complex “Car” object to the web method using javascript and webservice behavior.

//Javascript method which sends complex "Car" object as an argument to web service method.
function sendComplexObject()
{
    var ccar = new Object();
    ccar.Model = "Ferrari";
    ccar.CC = 2345;
    ccar.Color = "Ferrari Red";
    ccar.Blah = "dlkjs";
    var txtCntl = document.getElementById("divHTC");
    txtCntl.useService("http://localhost:3150/TestService.asmx?wsdl", "webServ");
    var uniqueID = txtCntl.webServ.callService(handleResult, "GetCar", ccar);
}
//Method which handles the response.
function handleResult(result)
{
    alert(result.value);
}

In the above “sendComplexObject” javascript function we are first creating a javascript object of type “Object”. Once the object of type “Object” is created, we are dynamically adding properties to the object. One thing to note here is that the dynamically added properties should have the same name as that of the server side object properties and it is case sensitive. If you closely notice the above code we have added an extra property called “Blah” to the “Car” object. Now, you may be thinking how is this object of “Object” type getting converted to a strongly typed .NET object. When the web method is invoked using “callService” method, the HTC file has got the necessary code to read the schema and infer the type of argument the method takes and converts the properties into a XML and sends to the web service method. In the web service side the .NET runtime takes care of serializing the XML to respective .NET object. We have added an additional “Blah” property to the object which during serialization is not converted as there is no corresponding server side property.

Note: If you have any value type in your complex object (like “CC” property in the “Car” object) then make sure that you assign them a value (default) even if you are not going to use it in the server side, else while invoking the web method the server can throw “System.FormatException”. The details of the exception which was thrown while not assigning the “CC” property of the “Car” object is pasted below.

System.Web.Services.Protocols.SoapException: Server was unable to read request. ---> System.InvalidOperationException: There is an error in XML document (7, 22). ---> System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
   at System.Xml.XmlConvert.ToInt32(String s)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read3_Car(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read8_GetCar()
   at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer8.Deserialize(XmlSerializationReader reader)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   --- End of inner exception stack trace ---
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()

How to handle complex objects returned from the server?

There can be scenarios where the server may return complex objects back to the caller. How to handle complex objects in javascript? Don’t worry with HTC file that is also a child’ play. Below is a web method which returns a complex “Car” object. We have slightly altered the “Car” class.

//Method that returns a complex Car object.
[WebMethod]
public Car GetCarObject()
{
    return new Car { Model = "Ferrari", Color = "Ferrari Red", CarEngine = new Engine { CC = 2500, Cylinders = 8 } };
}

//Car class.
public class Car
{
   public string Model
   {   get; set; }
   public string Color
   { get; set; }
   public Engine CarEngine
   { get; set; }
}
//Engine class which is a property in Car class
public class Engine
{
   public int CC
   { get; set; }
   public int Cylinders
   { get; set; }
}

In the web method we are creating an instance of the “Car” class and returning it. The “Car” class is quite simple, it has a number of properties and in those properties one of the property is again a complex object of type “Engine”. Below is the javascript code which handles the complex object returned from the web service.

function receiveComplexObject()
{
    var txtCntl = document.getElementById("divHTC");
    txtCntl.useService("http://localhost:3150/TestService.asmx?wsdl", "webServ");
    var uniqueID = txtCntl.webServ.callService(handleComplexObject, "GetCarObject");
}
//Function handling the complex object.
function handleComplexObject(result)
{
    var carObj = result.value;
    var carProperties = "Model: " + carObj.model + ", Color: " + carObj.Color + ", CarEngine.CC: " + carObj.CarEngine.CC + ", CarEngine.Cylinders: "
                                + carObj.CarEngine.Cylinders;
    alert(carProperties);
}

handleComplexObject” function handles the complex “Car” object returned from the server. The “value” property of the result object holds the complex object. You can access the various properties and fields in a complex object by specifying the names of the properties or fields as done in the above e.g.

How to handle server errors in client Side?

We have seen the different ways of invoking a web method with parameters, without parameters and with complex parameters. As we all know everything doesn’t work according our plan, so what happens if the server throws an error? Handling errors from server is also pretty much simple.The method which is specified in “callService” method to handle the result takes an argument. The result object passed as an argument to the callback function which handles the request from the service has got the necessary information to handle errors. Lets see with e.g.

//Javascript function which is supposed to throw error.
function sendComplexObject()
{
    var ccar = new Object();
    ccar.Model = "Ferrari";
    ccar.Color = "Ferrari Red";
    var txtCntl = document.getElementById("divHTC");
    txtCntl.useService("http://localhost:3150/TestService.asmx?wsdl", "webServ");
    var uniqueID = txtCntl.webServ.callService(handleError, "GetCar", ccar);
}
//Function which handles server side error.
function handleError(result)
{
    if (result.error)
    {
        var errorDetails = "Error code: " + result.errorDetail.code;
        errorDetails += "\nError string: " + result.errorDetail.string;
        errorDetails += "\nError soap format: " + result.errorDetail.raw.xml;
        alert(errorDetails);
    }
    else
    {
       alert(result.value);
    }
}

The above pasted code is pretty straight forward. The “handleError” function like other callback function in this blog takes a result object as argument. The first line of the function checks whether there is any error in the result returned from the server by checking the “error” property of the result object. If there is an error the next three lines of code retrieve the error details by accessing the “errorDetail”  property’ “code”, “string” and “raw.xml” properties. The code property returns the code for the error, “string” property returns the error message and “raw.xml” returns the whole SOAP xml.

So that’ some of the key features of webservice behavior. In the next blog we will see how to make use of “ScriptManager” to invoke web methods. Till then try to know more.

Sandeep