Click here to Skip to main content
Click here to Skip to main content

Consuming .NET Web Services via the kSOAP library

By , 31 Oct 2008
 

Introduction

As many of you know, Web Services are a great way to establish communication between distant and independent platforms.

It is straightforward to create .NET Web Services, and easier to use them from any .NET Framework enabled system. Nevertheless, when the issue comes to a non-.NET Framework system, some interoperability difficulties may appear. In order to enhance the interoperability of Web Services, the WS-I publishes profiles. The following article uses the 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, a Java enabled Android OS installed Mobile Device will be used. For the 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 an 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 software which are used during this article, are listed below:

  • SOAP v 1.1
  • kSOAP v 2.1.1 (with the 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 the article was written, all of these software were freely downloadable from the 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.)

[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 = &quot;Event&quot;+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.)

public abstract class BaseObject implements KvmSerializable {
    public static final String NAMESPACE = &quot;http://tempuri.org/encodedTypes&quot;;
    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 the Client Side

Defining parameters for calling the SOAP RPC Web Service methods:

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

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; the default is 'http://tempuri.org/', and 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

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 via the addProperty() 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.

The first example is the GetGivenInt() Web Service method which gets a primitive int parameter and returns the same primitive integer value.

PropertyInfo pi = new PropertyInfo();

pi.setName(&quot;i&quot;);
pi.setValue(5);
request.addProperty(pi);

The 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.

PropertyInfo pi = new PropertyInfo();
pi.setName(&quot;date&quot;);
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 Event class.

PropertyInfo pi = new PropertyInfo();
pi.setName(&quot;evnt&quot;);
Event e = new Event();
e.setName(&quot;Antalya, Turkey&quot;);
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

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

In this example, SOAP version 1.1 is used, but both versions 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, the 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 the kSOAP library by default. The class that we are going to register for marshalling should implement the Marshal interface which has three important methods.

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

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

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

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

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

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

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

Marshal dateMarshal = new bilgiciftligi.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 KvmSerializable and its required methods for parsing. The mapping should be added before the Web Service call.

envelope.addMapping(BaseObject.NAMESPACE, &quot;Event&quot;, 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. The standard HttpTranport class is used for the call, but for the Android OS, we change 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.

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.

int receivedInt = (Integer)envelope.getResponse();
Log.v("BILGICIFTLIGI", receivedInt.toString());

Same rules apply for the complex and undefined simple types.

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

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

Vector<Event> receivedEvents = (Vector<Event>)envelope.getResponse();
if(receivedEvents != null)
{
    for(Event curEvent : receivedEvents)
    {
        Log.v("BILGICIFTLIGI", curEvent.toString());
    }
}

Tracing Request and 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 analyzers 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.

History

  • First commit created on 14 September 2008.

License

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

About the Author

sacoskun
Engineer
United States United States
Member
http://sacoskun.blogspot.com
http://findthefashion.com
http://ewenty.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionThe return object ksoap2 received from WCF service is anyTypemembermone7411204 May '12 - 23:20 
Excuse, me.
I refer your code and face a difficult problem.
I both declare complex class "Phone" contains two properties "Tag" and "Phone" in wcf servive and ksoap2 client.
The webmethod will return a Phone instance when receiving a parameter phone, we use a soapobject "evenlope" to receive the returned value like you,
 
(Phone) p = (Phone) evenlope.getResponse();
 
but the android client is freezed. I found the reason is that soapobject can not be cast to Phone successfully.
And then I check the responded XML in soapobject from WCF, the context is "anyType{tag=XXXX; PhoneNo=XXXXXXXXXX}". I don't know what is the problem in my code because I also try some setting to change the responded XML as "Phone{tag=XXXX; PhoneNo=XXXXXXXXXX}", but it still does not work.
I will appreciate if you can give a hand, thank you very much.
Generalmultiple layers of complex typememberdirsceol16 Aug '10 - 7:00 
I have implemented this example for my own soap service. It works for the top layer of complex types but they each have other complex types inside of them that seem to ignore the mapping .
 
envelope.addMapping(BaseObject.NAMESPACE, "Login", new Login().getClass());
envelope.addMapping(BaseObject.NAMESPACE, "Token", new Token().getClass());
envelope.addMapping(BaseObject.NAMESPACE, "LoginResult", new LoginResult().getClass());
envelope.addMapping(BaseObject.NAMESPACE, "LoginResponse", new LoginResponse().getClass());
 
I am sending in the Login class and getting back the LoginResponse class which has a LoginResult which has a Token. It throws a ClassCastException when it is trying to set the LoginResult inside of LoginResponse.setProperty.
 
public class LoginResponse extends BaseObject {
public LoginResult getLoginResult() {
return LoginResult;
}
 
public void setLoginResult(LoginResult loginResult) {
LoginResult = loginResult;
}
 
public static Class EVENT_CLASS = new Token().getClass();
private LoginResult LoginResult;


 
public Object getProperty(int index) {
switch (index) {
case 0:
return LoginResult;
default:
return null;
}
}
 
public int getPropertyCount() {
return 1;
}
 
public void getPropertyInfo(int index, Hashtable properties, PropertyInfo info) {
switch (index)
{
case 0:
info.type = LoginResult.class.getClass();
info.name = "LoginResult";
break;
default:
break;
}
}
 
public void setProperty(int index, Object value) {
switch (index) {
case 0:
LoginResult = (LoginResult)value;
break;
default:
break;
}
}
 

}
GeneralI have got values help me littlemembercutesamatwork13 Jul '10 - 19:27 
Hi sacoskun,
 
I have updated my code details here http://www.anddev.org/novice-tutorials-f8/ksoap2-complex-data-type-client-not-working-t15935.html
 
I am returning as vector in my webservice.I am getting those values.But not able to separate it.I tried as vector or other types in my client side.But it throws class cast exception. Confused | :confused: So help me to solve this problem..
GeneralRe: I have got values help me littlememberHMia30 Jul '10 - 3:22 
Hi!
v is ur vector
for(int i=0;i<v.size();i++)
{
Object[] obj=(Object[])v.elementAt(i);
//the obj contains the informations about ur object depending on their order

}
GeneralArray parametermemberfadi wedyan24 May '10 - 0:19 
Hi,
Thanks for the great article. I'm having a problem using a web service that has an array of objects paramter, I did serilaize the object but I dont know how to use the addMapping method for an array type, any ideas?
Thanks
GeneralComplex typesmemberHMia17 May '10 - 3:17 
Hello!
I'm using complex types to communicate with my wcf service via my android application.I have this error "cannot cast type" i followed the article but i didn't understand the part where we register and marshal the type could u plz give me some samples.
Thx
GeneralA note on the dotNet Flag....membersslipknot30007 Apr '10 - 6:04 
First, let me say that this was a very helpful article. Very few people out there attempt to address how to deal with complex-type parameters.
 
That said, we had a .NET method that looked like this....
 
[WebMethod, SoapRpcMethod]
public LoginResponse DoLogin( LoginRequest aLoginRequest )
{....}
 
We were having problems on the .NET Server side of the web service... the service was failing to populate the "aLoginRequest" parameter with the object sent from kSOAP. This resulted in a 500 Internal Server Error, object reference not set to an instance of an object. After much investigation... we discovered the following...
 
On the client side, when the "envelope.dotNet" flag is set to "true" or "false", the kSOAP library renders the resulting SOAP message differently.

In your .NET service, when you get rid of this line....
 
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 
...and you add the "SoapRpcMethod" attribute to your .NET web method, this causes .NET to change it's expected SOAP request to a more generic "non-dot-net" request.
 
Because of this, you should set "envelope.dotNet = false" on your Java client if you are using the "SoapRpcMethod" attribute, and get rid of the WsiProfiles directive.
 
I know it sounds funny setting .dotNet = false, while consuming a .NET web service, but once we did this, our 500 error went away.
 
I hope this helps anyone that runs into the same issue.
Generalhelp me , pleasememberKarla O´neill24 Mar '10 - 4:31 
Hi there!Maybe you could give some help.... I need to call a .NET C# web service using ksoap2, from Android!
 
My web service contains a class returning a string and a integer like this :
 

public Data(string _name, int _yearOfBirth)
{
Name = _name;
YearOfBirth = _yearOfBirth;
}
 
I am a beginner in Software Development, so, how can I call it from Android?
 
SoapObject Request = new SoapObject(NAMESPACE,METHOD_NAME);
Request.addProperty("Name","Lucy"):
Request.addProperty("YearOfBirth",1985);
 
It doesn't work like this... So how can I instantiate, in Android, an object of Data Type and pass it to the web service?
 

thank you so much
Generalthanks...memberOrian Beltrame5 Feb '10 - 7:55 
Hi, this article helped me a lot.
but would you post the solution for download. I'm having problems with complex types on request ..
 

thanks
 
Orian - C++ next step.

GeneralGreat articlememberPredrag Tomasevic28 Dec '09 - 10:04 
I'm sorry to see low score for such a great article.
 
I've also experienced array/Vector problem... here is how I solved it:
 
Object o = (Object)envelope.getResponse();
 
// it's SoapObject only for arrays
SoapObject obj = (SoapObject)o;				
 
String[] res = new String[obj.getPropertyCount()];
for (int i = 0; i < res.length; i++) {
	res[i] = obj.getProperty(i).toString();
}
 
return res;

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 31 Oct 2008
Article Copyright 2008 by sacoskun
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid