Consume SOAP web services in Dot.net

Has an anyone managed to consume web services defined in the RO service builder and dispatched as a ROSoapMessage in Dot.net.

The documentation suggests modifying soap mode and the serialization options to to make them more compatible to 3rd party clients.

Setting
SoapMode = 2 which is DocumentLiteral mode
xsoSplitServiceWsdls => True

We have also tried most other combinations of the following options:-
xsoClientIdInWsdl, xsoDocument, xsoDocumentLiteralWrappedNaming, xsoEncodedXML
xsoExternalTypesAsReferences, xsoIgnoreStructureType,
xsoSendUntyped, xsoSkipOperationName, xsoStrictStructureFieldOrder
xsoSoap12, xsoWriteMultiRefArray, xsoWriteMultiRefObject

Helli

By consuming in .NET you mean ‘consuming as a web service, without the SDK components’?
This old article should help you: http://old.wiki.remobjects.com/wiki/WIP:How_to_consume_RemObjects_SOAP_services_with_third_party_clients

Regards

@antonk
Is this old article still available?

This article assumes that the SOAP server is running on the localhost at the port 8099 and the SOAP Message dispatcher is configured by default so the path is /soap. As a result the service endpoint URL is: http://localhost:8099/soap

Obtaining WSDLs
Before you create any SOAP client you need to obtain the service’s WSDL definition. The URL where this definition can be obtained differs depending on the number of services published by the server and the server setup:

  • If there is only one service published then the WSDL can be obtained at http://localhost:8099/soap

  • If there are two or more services published and the server has default settings regarding WSDL splitting then open the http://localhost:8099/soap URL in your web browser and see all services listed on the page. Names of these services contain the links to URL of each service’s WSDL. In general a WSDL URL is formed by appending the service name as a parameter to the base URL, for example http://localhost:8099/soap?service=LoginService. Save all WSDL URLs for later use.

  • If you have modified the server’s settings to combine all published WSDLs then the WSDL URL is still http://localhost:8099/soap for any number of published services.

Session Identifiers
Sessions are used to make some data on the server persist between the client connections. Also sessions are used to build secure services. If you require this functionality you have to pass the session identifier GUID (commonly referenced as SessionID) from the client to the server. RemObjects BinMessage messaging protocol has a built-in field for transferring that but the commonly used protocols don’t. As for SOAP we work this around by placing the SessionID into the SOAP header for both request and response, respectively:

<source lang="xml"> 
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
	<s:Header>
		<h:ROClientIDHeader xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
			<ID>{ae5d420e-377a-4648-8cb9-5f7117fda149}</ID>
		</h:ROClientIDHeader>
	</s:Header>
	<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
		<LoginService___Login xmlns="http://tempuri.org/"/>
	</s:Body>
</s:Envelope>
</source>

<source lang="xml">
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance/" xmlns:HNS="http://tempuri.org/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://tempuri.org/">
	<SOAP-ENV:Header>
		<ROClientIDHeader SOAP-ENV:mustUnderstand="0" xmlns="http://tempuri.org/">
			<ID>{AE5D420E-377A-4648-8CB9-5F7117FDA149}</ID>
		</ROClientIDHeader>
	</SOAP-ENV:Header>
	<SOAP-ENV:Body xmlns:ro="http://tempuri.org/">
		<v1:LoginService___LoginResponse/>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
</source>

Note the ROClientIDHeader and the ID parameter. This is the SessionID. It is advised for the client side to generate and pass the SessionID with the first call to the server. If non-empty GUID is passed to the server it is accepted as a new ID for the session. In this case there is no need to parse the returned SessionID back. But is you don’t pass a SessionID or pass an empty GUID then the server generates one for you and you have to parse it and save for use in subsequent calls. The SessionID must be stable during the whole client session.

According to SOAP Message Options set on the server side the information about the SessionID header may or may not be included into the WSDL service definition, see details below.

Preparing Server
Besides the channel and message configuration that is done for any RO SDK server two options should be configured for the SOAP server:

  • SessionID header publication to the WSDL definition. If you don’t use sessions in your application you don’t care about this setting. Otherwise it is strongly recommended to turn it on as some SOAP consuming facilities (WCF for instance) may experience difficulties operating with the SOAP header data.
  • WSDL splitting. By default every service published by the server has its own separate WSDL definition data accessible by different URLs. You can configure the server to combine them.

Delphi

You need to change the properties of the TROSOAPMessage instance with the designer or in run time. For the designer first set the SoapMode to sDocumentLiteral. Then expand SerializationOptions and set xsoClientIdInWsdl to true. And finally turn on xsoSplitServiceWsdls

.NET
Here you operate with properties of the SoapMessage instance with the designer or in run time. For the designer first set the SoapMode to DocumentLiteral. Then modify the SerializationOptions property. The property is a comma-separated list of flags set to true. Make sure you include ShowClientID to publish the header content information to WSDL. If you want to disable the WSDL splitting then include SplitServiceWsdls.

As you can see, the flags designer is not so convenient to use and you may prefer setting flags at run time:

public MainForm()
{
	//
	// Required for Windows Form Designer support
	//
	InitializeComponent();
	
	// Configuring the SOAP message
	soapMessage1.SerializationOptions |= SoapSerializationOptions.ShowClientID;
}

Creating Clients
As said, the RO SDK SOAP service can be consumed by any SOAP aware application from any platform. In this article we discuss using Visual Studio native facilities to access a RO SDK server via SOAP without using RO SDK on the client side.

Visual Studio provides two facilities for consuming SOAP services: Service References and Web References. The latter is .NET 2.0 based, considered legacy and provided for compatibility with the legacy projects. We recommend using Service References.

Right-click the References node in your project and select the Add Service Reference command. The Add Service Reference dialog appears.

Enter the service WSDL address to the Address field. If you enter a wrong URL by mistake you will probably see a error message like this:

The HTML document does not contain Web service discovery information.
Metadata contains a reference that cannot be resolved: 'http://localhost:8099/soap'.
The content type text/xml; charset=utf-8 of the response message does not match the content type of the binding (application/soap+xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.

Specify the Namespace for the service reference, this namespace will be available in your project. After successful import you will see the Service References folder added to the project. This folder contains a service reference named as you specified in the Namespace field.

If your server has more than one service published and it is not configured to combine WSDLs (the default setting is not to combine) repeat the Add Service Reference operation for all services you need.

Now it’s time for some coding. The service usage is easy (assume our WorkingService is secure and required LoginService to be called for authentication). First we prepare some object required for the WCF to operate, notice your server SOAP URL:

System.ServiceModel.Channels.Binding b = (System.ServiceModel.BasicHttpBinding)Activator.CreateInstance(typeof(System.ServiceModel.BasicHttpBinding));
System.ServiceModel.EndpointAddress eAddress = new System.ServiceModel.EndpointAddress("http://localhost:8099/soap");

Now prepare WCF clients for both services:

LoginReference.LoginServiceClient loginClient = new LoginReference.LoginServiceClient(b, eAddress);
WorkingReference.ContentTypeTestServiceClient workingClient = new WorkingReference.ContentTypeTestServiceClient(b, eAddress);

The next step is quite important - we prepare the SessionID and wrap it into the structure that corresponds to the dedicated SOAP header part:

var loginHeader = new LoginReference.ROClientIDHeader();
loginHeader.ID = String.Concat("{", Guid.NewGuid().ToString(), "}");
WorkingReference.ROClientIDHeader workingHeader = new WorkingReference.ROClientIDHeader();
workingHeader.ID = loginHeader.ID;

Note two things here:

  • The SessionID parameter is a string for SOAP and it must be enclosed into curly braces
  • There are two different ROClientIDHeader classes meaning the same but from different services (and defined in different namespaces). They both must contain the same SessionID, i.e. ID property value.

And finally we use both services, logging in first, then calling the working service. You can the structures responsible for the SOAP header are passed using ref so something may be return back from the server. But as was said above, if we specify a non-empty SessionID we don’t need to get the SessionID returned by the server.

loginClient.LoginService___Login(ref loginHeader);
var res = workingClient.ContentTypeTestService___GetServerTime(ref workingHeader);
MessageBox.Show(res.ToString());