Consuming .NET Web Services via kSOAP Library

I have seen that many people having difficulties on consuming .NET web services via Android mobile devices.

I wrote an article on codeproject.com about this issue more than a year ago but many people do not know about it and still ask me.

Here is the original link for the article;

http://www.codeproject.com/KB/mobile/CallWebServiceFromkSOAP.aspx


Introduction

As many of you know web services are the great way to establish communication between distance and independant platforms.

It is straightforward to create .NET web services and easier to use them from any .NET Framework enabled system. Nevertheless, when the issue come to a non-.NET Frameworked system, some interoperability difficulties may appear. In order to enhance the interoperability of web services, the WS-I publishes profiles. The following article will be using RPC(Remote Procedure Call) messaging pattern of the widely used profile, SOAP(Simple Object Access Protocol) with .NET web services on the server side.

On the client side, Java enabled Android OS installed mobile device will be used. For Android OS we need a web service client library that is specially designed for constrained Java environments and kSOAP provides this facility for us in open source way!

Main purpose of the article is to demonstrate how to write a .NET web service that can communicate with an Android OS through kSOAP library.

Required Technologies

The versions of the softwares which are used during this article, are listed below;

  • SOAP v 1.1
  • kSOAP v 2.1.1 (patched with WSDL patch)
  • Microsoft .NET Framework 2.0 SDK
  • Sun Microsystems Java Development Kit 1.6.0
  • Android SDK m5-rc15 for Linux-x86

(At the time of the article is written, all of these softwares were freely downloadable from internet.)

Using the code

Web Service Definition(in .NET)

The source code file of the web service is given below. The important thing is all the method names should be unique, even if the method signatures are different. SOAPAction values must be unique across the namespace.

(imports are not shown for brevity.)

Collapse
[WebService(Namespace = "http://tempuri.org/")]  public class Service : System.Web.Services.WebService  {      public Service(){}      [SoapRpcMethod(), WebMethod]      public int GetGivenInt(int i)      {          return i;      }      [SoapRpcMethod(), WebMethod]      public Event GetGivenEvent(Event evnt)      {          return evnt;      }       [SoapRpcMethod(), WebMethod]      public int[] GetGivenIntArray(int[] array)      {          return array;      }       [SoapRpcMethod(), WebMethod]      public DateTime GetGivenDate(DateTime date)      {          return date;      }       [SoapRpcMethod, WebMethod]       public Event[] GetOnGoingEvents()      {          Event[] arrayToReturn = new Event[100];          Event e ;          for(int i = 0; i < 100; i++)          {          e = new Event();          e.Name = "Event"+i;          e.StartDate = new DateTime(2008, 6, 12);          e.EndDate = new DateTime(2008, 6, 20);          e.SubscriptionStartDate = new DateTime(2008, 3, 12);          e.SubscriptionEndDate = new DateTime(2008, 4, 12);          arrayToReturn[i] = e;          }           return arrayToReturn;      }       // Custom defined inner class to represent complex type.     public class Event      {           // Generate properties for           // String Name,           // int Key,           // DateTime SubscriptionStartDate, SubscriptionEndDate, StartDate, EndDate       }  

Client Side Complex Type Definitions(in Java)

(imports are not shown for brevity.)

Collapse
public abstract class BaseObject implements KvmSerializable {     public static final String NAMESPACE = "http://tempuri.org/encodedTypes";     public BaseObject() {         super();     } }  public class Event extends BaseObject {     public static Class EVENT_CLASS = new Event().getClass();         private String name;     private int key;     private Date subscriptionStartDate;     private Date subscriptionEndDate;     private Date startDate;     private Date endDate;      @Override     public Object getProperty(int index)     {         switch (index) {         case 0:             return name;         case 1:              return key;         case 2:             return subscriptionStartDate;         case 3:             return subscriptionEndDate;         case 4:             return startDate;         case 5:             return endDate;         default:             return null;         }     }      @Override     public int getPropertyCount() {         return 6;     }      @Override     public void getPropertyInfo(int index, Hashtable properties, PropertyInfo info) {         switch (index)         {         case 0:             info.type = PropertyInfo.STRING_CLASS;             info.name = "Name";             break;         case 1:             info.type = PropertyInfo.INTEGER_CLASS;             info.name = "Key";             break;         case 2:             info.type = MarshalDate.DATE_CLASS;             info.name = "SubscriptionStartDate";             break;         case 3:             info.type = MarshalDate.DATE_CLASS;             info.name = "SubscriptionEndDate";             break;         case 4:             info.type = MarshalDate.DATE_CLASS;             info.name = "StartDate";             break;         case 5:             info.type = MarshalDate.DATE_CLASS;             info.name = "EndDate";             break;         default:             break;         }     }      @Override     public void setProperty(int index, Object value) {         switch (index) {         case 0:             name = value.toString();              break;         case 1:              key = Integer.parseInt(value.toString());             break;         case 2:             subscriptionStartDate = (Date)value;             break;         case 3:             subscriptionEndDate = (Date)value;             break;         case 4:             startDate = (Date)value;             break;         case 5:             endDate = (Date)value;             break;         default:             break;         }     } // Getters and setters are omitted for brevity. } 

Defining Web Service Properties from Client Side

Defining parameters for calling the SOAP RPC web service methods.

Collapse
    private static final String SOAP_ACTION = "http://tempuri.org/MethodName";     private static final String METHOD_NAME = "MethodName";     private static final String NAMESPACE = "http://tempuri.org/";         private static final String URL = "http://192.168.2.200/Service.asmx"; 


All of the data above can be retrieved from the web service definition(WSDL).
METHOD_NAME is the name of the method that we define in the web service.
NAMESPACE is the namespace of the web service, default is “http://tempuri.org/”, can be specific to your own organization.
SOAP_ACTION is the direct concatenation of NAMESPACE followed by METHOD_NAME.
URL is the location where the web service can be accessed from. If the connection will be through SSL, you need to specify it here.(e.g. https)

Set the Arguments to Pass

Collapse
            SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);     


After defining our SoapObject, we can add the arguments that we are going to send to the web service method viaaddProperty() method.

If the web service method does not require any parameters, no need to add any property. If one or more parameters are required, the important thing while passing a parameter is the PropertyInfo's name and type should match with the original web service method's parameter names.

First example is GetGivenInt() web service method which gets a primitive int parameter and returns the same primitive integer value.

Collapse
            PropertyInfo pi = new PropertyInfo();              pi.setName("i");             pi.setValue(5);                         request.addProperty(pi);      

Second example web service method is GetGivenDate() which gets a DateTime parameter and returns the same value. We need to add marshalling for the simple types that are not standardized in kSOAP, I will talk about it in the next part.

Collapse
            PropertyInfo pi = new PropertyInfo();             pi.setName("date");             pi.setValue(new Date(System.currentTimeMillis()));                         request.addProperty(pi);      

Another example is a complex type, which is going to be sent as a parameter to the GetGivenEvent() web service method which returns the same Event object back. Since the type Event is complex, we set the type as the Eventclass.

Collapse
            PropertyInfo pi = new PropertyInfo();             pi.setName("evnt");             Event e = new Event();             e.setName("Antalya, Turkey");             e.setKey(1);             e.setEndDate(new Date(EndDate.timeMillis()));             e.setStartDate(new Date(StartDate.timeMillis()));             e.setSubscriptionEndDate(new Date(SubscriptionEndDate.timeMillis()));             e.setSubscriptionStartDate(new Date(SubscriptionStartDate.timeMillis()));             pi.setValue(e);             pi.setType(Event.EVENT_CLASS);             request.addProperty(pi); 

Set Up the Envelope

Collapse
            SoapSerializationEnvelope envelope =                  new SoapSerializationEnvelope(SoapEnvelope.VER11);             envelope.dotNet = true;             envelope.setOutputSoapObject(request); 

In this example SOAP version 1.1 is used but both version 1.1 and 1.2 are supported by the .NET Framework. The dotNet flag needs to be true for a .NET web service call from kSOAP2. In the end SoapObject instance “request” is assigned as the outbound message of the soap call to the envelope.

Add Necessary Marshals

Marshalling is even required for simple types if they are not defined by kSOAP library by default. The class that we are going to register for marshalling should implement the Marshal interface which has three important methods.

readInstance() method is required to parse the xml string to the simple type when a response is retreived. Here is the given example of MarsalDate class, stringToDate() method should be changed to your defined type parsing method.

Collapse
   public Object readInstance(XmlPullParser parser, String namespace, String name,                       PropertyInfo expected) throws IOException, XmlPullParserException {         return IsoDate.stringToDate(parser.nextText(), IsoDate.DATE_TIME);    }  

writeInstance() method is required to parse simple type to xml string while sending a request. The given example is from MarsalDate class, for other types the parsing method should be implemented by yourself.

Collapse
   public void writeInstance(XmlSerializer writer, Object obj) throws IOException {        writer.text(IsoDate.dateToString((Date) obj, IsoDate.DATE_TIME));    }  

register() method tells the envelope that all the xml elements suit this namespace and name will be marshalled by the given class.

Collapse
   public void register(SoapSerializationEnvelope cm) {        cm.addMapping(cm.xsd, "dateTime", MarshalDate.DATE_CLASS, this);    } 

Before calling the web service appropriate marshals should be registered to the envelope, otherwise either request or respond will give parsing errors.

Collapse
            Marshal dateMarshal = new findthefashion.serialization.MarshalDate();             dateMarshal.register(envelope);

Add Necessary Mapping

Mapping is required for complex type object parsing. The idea is similar with marshalling but the complex type object should implement the KvmSerializable and its required methods for parsing. The mapping should be added before the web service call.

Collapse
            envelope.addMapping(BaseObject.NAMESPACE, "Event", new Event().getClass()); 

Invoke the Web Service Method

After setting the parameters, marshals and mappings we are ready for a call to the web service. StandardHttpTranport class is used for the call, but for the Android OS we changed some parts of the call for tracing that's why it is called AnroidHttpTransport. However, the main idea is the same. Providing the SOAP_ACTION, URL and the envelope to the call will be enough.

Collapse
            AndroidHttpTransport androidHttpTransport = new AndroidHttpTransport(URL);             androidHttpTransport.call(SOAP_ACTION, envelope); 

Parse the Response

If the response in the envelope is not an array, after getting the response we can directly cast it to desired type.

Collapse
            int receivedInt = (Integer)envelope.getResponse();             Log.v(“FINDTHEFASHION”, receivedInt.toString());  

Same rules apply for the complex and not defined simple types.

Collapse
            Date receivedDate = (Date)envelope.getResponse();             Event receivedEvent = (Event)envelope.getResponse(); 

If the response in envelope is an array of any type(complex or primitive), the casting should be done into a Vector at first. By using the power of generics, we can define a Vector which contains the desired type.

Collapse
            Vector receivedEvents = (Vector)envelope.getResponse();             if(receivedEvents != null)             {                 for(Event curEvent : receivedEvents)                 {                     Log.v(“FINDTHEFASHION”, curEvent.toString());                 }             } 

Tracing Request & Response

The typical problem while creating a web service request call or getting a response is tracing the ongoing data. All the important data is moving between network interfaces and the exception that is thrown in the application may not be so helpful sometimes. Therefore, a packet sniffer application is required to trace all the steps and do not miss anything. Wireshark(formerly Ethereal) is one of the best network protocol analyzer which can help you on this issue. The Wireshark project is open source and binaries are freely available. I bet it will be your best friend while tracing.

Comments

Davidasahuman said…
Do you have an example project available to download?

Thanks
sacoskun said…
Sorry David, I don't have one handy.
If I find it, I will let you know.
MaxS said…
Hi, first I've found your article on codeproject and then I've got one question, so I suppose it's easier to contact you via your blog.
Here it is:
If I recieve responce in the following form:



NumberOfError
SomeMessage



Then do I need to create wrapper types either for error and message tags and add a mapping to envelope?
MaxS said…
Sorry, I meant

‹result›
‹error›NumberOfError ‹⁄error›
‹message›SomeMessage ‹⁄message›
‹⁄result›
Unknown said…
Hello,
this tutorial is very nice....
So, please I have a very big problem to connect to WCF.
Please can I send you the WSDL and my code for your help?
I have now lost 2 weeks for that and and I am really tired now...
If you are ready to help me, please say me at diengsallah@gmail.com
Thank in advance
Anonymous said…
How about a Java example using a secured web service where in C# you would pass an ICredential ?
heart disease said…
I like your website, thank you for this information
Jim said…
What about a user defined type inside another user defined type? Do I have to marshal and map them?
Pinar Gokkaya said…
Hi,
this step by step helped me a lot to understand how to set the SOAP object. But I have a little problem with complexTypes. Somehow the service doesn't recognize the request object I am sending and returning me some kind of return code meaning I didn't send anything.
Do you have any idea of what might be going wrong?
I can send you my source code if you have time to look at it.

pinar.gokkaya@gmail.com
careprost said…
Great
loved it, will be waiting for your future posts
Thank you for sharing
thank you for sharing such a great post its really awesome
Anonymous said…
THnx............... a ton.
gr8 tut...helped me a lot
marca personal said…
found good piece of information here, Thank you for sharing these helpful post it is very interesting and read worthy. Keep these informative post coming!
Interesting read!!! Your website is fantastic with informative content which I like to add to my favourites.
Hi, im tired in this ksoap.











I dont know how to parse this type of xml as output from .net. please help me

Thanks in advance
rmouniak said…
Nice work, your blog is concept oriented ,kindly share more blogs like this
.NET Online Course Bangalore

Popular posts from this blog

Space Character Problem on IE 6, 7, and 8

AWS encryption chart (SSE-S3 vs SSE-KMS vs SSE-C)

Does Netflix work on iOS 5 Beta 4?