views:

1503

answers:

2

Hi everyone.

I'm having a bit of trouble getting Visual Studio to play nicely with my Axis2 web service. The problem is very strange, although not a show stopper. If anything it is just annoying, and I'd really appreciate a way around this.

The problem is that when using the web service in C# code, none of the parameters or return values are in their native data types. So instead of just being able to call, for example:

string timeDiff = MyWebService.GetServerTimeDifference(LocalTime)
Console.WriteLine(timeDiff);

I have to write

MyWebService.GetServerTimeDifference request = new MyWebService.GetServerTimeDifference();
request.@LocalTime = LocalTime;
Console.WriteLine(MyWebService.GetServerTimeDifference(request).@return);

As you can tell, this gets very annoying very quickly. The strange thing is that when creating the Web Reference to the web service, all of the data types and parameters are correctly shown in the service discovery page. I have tried modifying the WSDL file for the web service to remove anything which may be confusing visual studio, but so far I haven't been able to get this to work as it should.

I read somewhere that this is a Visual Studio and/or .Net problem in the de-serialization process, rather than a problem with the web service itself. I'm thinking this may be true, as the web service can be consumed correctly within NetBeans.

The Web Service is written in Java and hosted on an axis2 / Tomcat server, but the client software will be written in C# .Net 2.0.

Anyway - has anybody experienced this before? I have been unable to find the page where I read about the de-serialization problem again, so if anybody has anything to say which could help me out I'd very much appreciate it.

A: 

I read somewhere that this is a Visual Studio and/or .Net problem in the de-serialization process, rather than a problem with the web service itself. I'm thinking this may be true, as the web service can be consumed correctly within NetBeans.

I have had this problem before - it's a .Net problem. My approach was to take a sledgehammer to the ant as it were and re-write the service in .Net

PSU_Kardi
Updating to axis2 seemed to help somewhat, but the core problem seems to remain - now .Net won't send parameters to the web service correctly, and cannot receieve return values.Unfortunately re-writing the web service in .Net isn't really a possibility for us as the web service needs to run on a Linux server. I could use Mono I suppose - but I really don't want to go down that route.
TomFromThePool
Have you tried hand inspecting the WSDL and everything? Sometimes the axis2wsdl misses out on some stuff. Forgets to make something an Array or gives the wrong value
PSU_Kardi
+1  A: 

I suggest you define your WSDL using the document/literal/wrapped style, which as far as I know seems to be the best fit when you want interoperability.

This makes your service implementation behave well with WCF, that is used by Viusual Studio 2008 when you define a Service Reference.

It is possible to modify the WSDL specifikation for your service without breaking an existing implementation, but don't count on it.

The tricky part, though, is that you have to use some special lingo in your WSDL so that WCF will not snap out of generating nice wrappers as you requested. In your case, the automatically generated client code seems to fall back to the document/literal style, where you create and initialize "structs" that you feed to your client service method.

In essence, what you need to do is to:

  1. Define your wsdl:types using XML Schema element.
  2. Stick to a reasonable subset of XML Schema constructs when defining types (see the WCF documentation for a list).
  3. Declare all elements of object-type (such as xsd:token, xsd:NMTOKEN, xsd:base64Binary, ...) as nillable="true" - plain types such as xsd:int should not be nillable.
  4. Be prepared that the first element in a sequence used as an answer will be returned from the service call while the rest will be passed as out parameters - this typically makes an xsd:int status a suitable candidate as first in sequence.
  5. Define your wsdl:message using wsdl:part named parameters (not parameter, not param, it has to be parameters) and using the element attribute
  6. Define the style of the soap:operation in the wsdl:binding as "document".
  7. Declare wsdl:input and wsdl:output to use "literal" SOAP encoding.

Play with your WSDL file and use svcutil to generate the client code. You will get warnings if there are errors in your WSDL file and if you look into the generated code, you will see comments that may point to why the wrapping style fails.

Some code may be of use here. This is a stripped down WSDL describing a service with one method GetVersionInformation that returns the triplet {"1.0", 1, 0} - effectively being the version of the interface using major and minor version numbers.

<wsdl:definitions 
    targetNamespace="http://tempuri.org"
    xmlns:tns="http://tempuri.org"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
 xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" 
 xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" 
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"&gt;

 <wsdl:types>
  <xsd:schema
            elementFormDefault="qualified"
            targetNamespace="http://tempuri.org"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;

            <!-- Wrapper elements. Conventions apply to wrapper element names! -->
            <xsd:element name="GetVersionInformation" nillable="true" type="tns:VoidType" />
            <xsd:element name="GetVersionInformationResponse" nillable="true" type="tns:VersionInformationType" />

            <!-- Just a void type -->
            <xsd:complexType name="VoidType">
                <xsd:sequence />
            </xsd:complexType>

            <!-- Major and minor version information -->
            <xsd:complexType name="VersionInformationType">
                <xsd:sequence>
                    <xsd:element nillable="true" minOccurs="1" maxOccurs="1" name="version" type="xsd:NMTOKEN" />
                    <xsd:element minOccurs="1" maxOccurs="1" name="major" type="xsd:int" />
                    <xsd:element minOccurs="1" maxOccurs="1" name="minor" type="xsd:int" />
                </xsd:sequence>
            </xsd:complexType>

  </xsd:schema>
 </wsdl:types>



    <!-- GetVersionInformation -->
    <wsdl:message name="GetVersionInformationSoapIn">
        <wsdl:part name="parameters" element="tns:GetVersionInformation" />
    </wsdl:message>

    <wsdl:message name="GetVersionInformationSoapOut">
        <wsdl:part name="parameters" element="tns:GetVersionInformationResponse" />
    </wsdl:message>




    <!-- Port type -->
 <wsdl:portType name="MyServicePortType">
        <wsdl:operation name="GetVersionInformation">
            <wsdl:input message="tns:GetVersionInformationSoapIn" />
            <wsdl:output message="tns:GetVersionInformationSoapOut" />
        </wsdl:operation>
 </wsdl:portType>



 <wsdl:binding name="MyServiceSOAP11Binding" type="tns:MyServicePortType">
  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />
        <wsdl:operation name="GetVersionInformation">
            <wsdl:input>
                <soap:body use="literal" parts="parameters" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
 </wsdl:binding>



 <wsdl:binding name="MyServiceSOAP12Binding" type="tns:MyServicePortType">
  <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />
        <wsdl:operation name="GetVersionInformation">
            <wsdl:input>
                <soap12:body use="literal" parts="parameters" />
            </wsdl:input>
            <wsdl:output>
                <soap12:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
 </wsdl:binding>



 <wsdl:service name="MyService">
  <wsdl:port name="MyServiceSOAP11port" binding="tns:MyServiceSOAP11Binding">
   <soap:address location="http://localhost:80/mojo/services/MyService" />
  </wsdl:port>

  <wsdl:port name="MyServiceSOAP12port" binding="tns:MyServiceSOAP12Binding">
   <soap12:address location="http://localhost:80/mojo/services/MyService" />
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>

You may generate client code for this WSDL, using:

  svcutil /t:code /serializer:DataContractSerializer /s  /out:MyService.cs /n:*,MyService /ixt MyService.wsdl

Further, calling this code from C# would be like this:

// The endpointConfigurationName must match the corresponding entry
// in app.config, with the following content:
//
//    <configuration>
//        <system.serviceModel>
//            <bindings>
//                <basicHttpBinding>
//                    <binding name="MyServiceSOAP11Binding" ...>
//                    </binding>
//                    .../...
//                </basicHttpBinding>
//            </bindings>
//            <client>
//                <endpoint 
///                  ... binding="basicHttpBinding" 
//                    ... bindingConfiguration="MyServiceSOAP11Binding"
//                    ... name="MyServiceSOAP11port" />
//            </client>
//        </system.serviceModel>
//    </configuration> 
//
string endpointConfigurationName = "MyServiceSOAP11port";
string wsEndpoint = "http://localhost/mojo/services/MyService";

MyService.MyServicePortTypeClient wsClient = null;
try
{
 wsClient = new MyService.MyServicePortTypeClient(endpointConfigurationName, wsEndpoint);
}
catch (InvalidOperationException ioe)
{
 // Possibly a problem with the configuration
 // Inform(Logging.LogLevel.WARNING, "Potential problem with configuration: " + ioe.Message);
 return;
}

string wsUsername = "John";
string wsPassword = "Doe";

if (!String.IsNullOrEmpty(wsUsername) && !String.IsNullOrEmpty(wsPassword))
{
 UserNamePasswordClientCredential credentials = wsClient.ClientCredentials.UserName;
 credentials.UserName = wsUsername;
 credentials.Password = wsPassword;
}

try
{
 int major;
 int minor;
 string version = wsClient.GetVersionInformation(out major, out minor);
 // Inform(Logging.LogLevel.DEBUG, "Service has version " + version);
}
catch (System.ServiceModel.EndpointNotFoundException enfe)
{
 // string info = "Could not contact MyService: " + enfe.Message;
 // Inform(Logging.LogLevel.ERROR, info);
 return;
}
catch (System.ServiceModel.FaultException fe)
{
 // string info = "Could not contact MyService: " + fe.Message;
 // Inform(Logging.LogLevel.ERROR, info);
 return;
}

While we are at it, why not also implement the service using Axis2. First, we need a service specification (services.xml in our AAR):

<serviceGroup name="MyServices">

    <service name="MyService" scope="application">
        <description>My Service - document/literal wrapped style, suited for .NET integration</description>

        <!-- Service methods -->
        <operation name="GetVersionInformation">
            <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
            <actionMapping>http://tempuri.org/MyServicePortType/GetVersionInformationRequest&lt;/actionMapping&gt;
        </operation>

        <!-- Use WS-Adressing, ... -->
        <module ref="addressing" />

        <!-- Service implementation -->
        <parameter name="ServiceClass">com.mycompany.services.MyService</parameter>
    </service>

    <service name="MyOtherService" scope="application" >
  .../...
    </service>

</serviceGroup>

And our server implementation, using AXIOM:

package com.mycompany.services.MyService;

import javax.xml.stream.XMLStreamException;
import javax.xml.namespace.QName;

import org.apache.axiom.om.*;
import org.apache.axis2.context.ServiceContext;
import org.apache.log4j.Logger;



public class MyService {
    public static final Integer MAJOR_VERSION = 1;
    public static final Integer MINOR_VERSION = 0;

    public static final String NAMESPACE = "http://tempuri.org";
    public static final String NAMESPACE_ALIAS = "tns";
    public static final String GET_VERSION_INFORMATION_RESPONSE_ELEMENT_NAME = "GetVersionInformationResponse";

    private ServiceContext serviceContext = null;
    private String serviceName = null;

    private static final Logger log = Logger.getLogger("SERVICE");

    public void init(ServiceContext serviceContext) throws Exception {
        this.serviceContext = serviceContext;
        serviceName = serviceContext.getName();
    }

    public OMElement GetVersionInformation(OMElement element) throws XMLStreamException {
        // --- Handle request ---
        String version = "" + MAJOR_VERSION + "." + MINOR_VERSION;

        if (log.isDebugEnabled()) {
            log.debug("Retrieving version information: " + version);
        }

        // --- Prepare response ---
        OMFactory factory = OMAbstractFactory.getOMFactory();
        OMNamespace omNs = factory.createOMNamespace(NAMESPACE, NAMESPACE_ALIAS);

        //
        OMElement response = factory.createOMElement(GET_VERSION_INFORMATION_RESPONSE_ELEMENT_NAME, omNs);
        {
            OMElement value;
            {
                value = factory.createOMElement("version", omNs);
                value.addChild(factory.createOMText(value, version));
                response.addChild(value);
            }
            {
                value = factory.createOMElement("major", omNs);
                value.addChild(factory.createOMText(value, "" + MAJOR_VERSION));
                response.addChild(value);
            }
            {
                value = factory.createOMElement("minor", omNs);
                value.addChild(factory.createOMText(value, "" + MINOR_VERSION));
                response.addChild(value);
            }
        }
        return response;
    }
}

Axis2 and Axiom are really nice to work with. If you are having problems to generate your C# client, revisit your WSDL - it is unlikely that the problem resides with Axis2. By the way, the "Adressing" module that we refer to in the service configuration is usually added by default, but there are other modules that may be used to handle other parts of the WS-I standard.

Frode Randers