Tuesday, March 10, 2009

WCF Service and Adobe LiveCycle Interoperability


I needed to expose a few WCF services to be consumed by Adobe LiveCycle server and ran into certain interoperability issues. With the help of some very useful blog posts and sample code (provided along with the blog posts) I was able to get past the hurdle. In this post I have compiled the complete details of the problem, solution and the references used.


The root of the problem was WSDL generated by WCF, by default the WSDL generated for a WCF service can consist of multiple files. In certain situations the main WSDL file can include a reference to another WSDL file which in turn refers to separate XSD files to describe the data types used in ServiceContracts. When client consuming the service is developed using .NET then .NET tools are able to process such WSDL (spanning multiple files) without any problem, it might not be a problem for client developed using some other technologies either but in our case the Adobe LiveCycle server was not able to process such WSDL correctly. So, the solution was to flatten the WSDL into one long document containing everything.


Problem Definition


Now, I will explain the problem we are trying to solve using some code. The sample I am using is again from Learning WCF by Michele Bustamante.


One of the DataContracts:

[DataContract(Namespace=http://schemas.thatindigogirl.com/samples/2006/06")]
public class PhotoLink: LinkItem
{
public PhotoLink()
{
this.LinkItemType= LinkItemTypes.Image;
}
}

ServiceContract:

[ServiceContract(Name="PhotoUploadContract",Namespace="http://www.thatindigogirl.com/samples/2006/06")] public interface IPhotoUpload
{
[OperationContract]
void UploadPhoto(PhotoLink fileInfo, byte[] fileData);
}

The WSDL generated for the above Service without any customization consists of a total of five files - two WSDL files and three XSD files. Instead of showing the complete text of the WSDL files I will just show the relevant parts.

The first WSDL file generated from MEX endpoint is relatively short and contains the following import statement:

<wsdl:import namespace="http://www.thatindigogirl.com/samples/2006/06" location="http://localhost:3107/PhotoApplication/PhotoManagerService.svc?wsdl=wsdl0" />

The location attribute points to the second WSDL file and this second WSDL file further refers to external XSD files:

<wsdl:types> <xsd:schema targetNamespace=http://www.thatindigogirl.com/samples/2006/06/Imports"> <xsd:import schemaLocation="http://localhost:3107/PhotoApplication/PhotoManagerService.svc?xsd=xsd0" namespace="http://www.thatindigogirl.com/samples/2006/06" /> <xsd:import schemaLocation="http://localhost:3107/PhotoApplication/PhotoManagerService.svc?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> <xsd:import schemaLocation="http://localhost:3107/PhotoApplication/PhotoManagerService.svc?xsd=xsd2" namespace="http://schemas.thatindigogirl.com/samples/2006/06" /> </xsd:schema></wsdl:types>

The schemaLocation attributes provide the location of external XSD files.

Solution Step 1


I used quite a few blog posts but the following article by Christian Weyer was most helpful, you can also download code for free which will flatten out the generated WSDL.


Link to the post: Improving WCF Interoperability: Flattening your WSDL

Christian Weyer showed multiple solutions to the tackle the problem:

  • A custom endpoint behavior.
  • A custom ServiceHost.
  • A custom ServiceHostFactory to be used when service is hosted in IIS (this was my case).

I used the custom ServiceHostFactory to host the service and was able to solve the problem partially, this time WSDL generated consisted of only two files. The first WSDL file referenced the second WSDL file and all the XSD schema definitions were included in line in the second WSDL file. This is one step forward but still it did not solve our problem completely because Adobe LiveCycle required all the content to be in one WSDL file.


The first WSDL file stays the same as before and refers to the second WSDL file, but the second WSDL file contains all XSD schemas inline as shown below:

<wsdl:types> <xsd:schema elementFormDefault="qualified" targetNamespace="http://www.thatindigogirl.com/samples/2006/06">; <xsd:element name="UploadPhoto"> <xsd:complexType> <xsd:sequence> <xsd:element minOccurs="0" name="fileInfo" nillable="true" type="q1:PhotoLink" xmlns:q1="http://schemas.thatindigogirl.com/samples/2006/06" />; <xsd:element minOccurs="0" name="fileData" nillable="true" type="xsd:base64Binary" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="UploadPhotoResponse"> <xsd:complexType> <xsd:sequence /> </xsd:complexType> </xsd:element> </xsd:schema> <xsd:schema elementFormDefault="qualified" targetNamespace="http://schemas.thatindigogirl.com/samples/2006/06" xmlns:tns="http://schemas.thatindigogirl.com/samples/2006/06">
...
...
...
. ..</wsdl:types>

Solution Step 2

Two more changes were required to get all the WSDL generated in one file:
1. Service Endpoint needs to be in the same namespace as the ServiceContract. Example, the endpoint for the service was configured as follows:

<endpoint contract="PhotoManagerService.IPhotoUpload" binding="basicHttpBinding" bindingNamespace="http://www.thatindigogirl.com/samples/2006/06">

2. A ServiceBehavior needs be to the service implementation type with the same namespace as ServiceContract. Example, Service implementation was modified to include a ServiceBehavior attribute as follows:
[System.ServiceModel.ServiceBehavior(Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public class PhotoManagerService: IPhotoUpload
{
public void UploadPhoto( ContentTypes.PhotoLink fileInfo, byte[] fileData)
{
PhotoUploadUtil PhotoUploadUtil = new PhotoUploadUtil();
photoUploadUtil.SavePhoto(fileInfo, fileData);
}
}


With the above two changes in place along with custom ServiceHost, desired results were achieved and the entire WSDL generated including XSD schemas was in one long file.

Please Note: There was no need to change namespace of the DataContract or the Mex Endpoint.


Conclusion

I was not too happy to discover this issue, wish there was simpler configuration based solution to generate WSDL in one document but the good thing is that WCF is extensible enough that if we dig beneath the surface desired solutions can be developed. Hence, I won't jump on the bandwagon that WCF is not ready for interoperability.

1 comment:

  1. Good follow up on Christian Weyer's flattening WSDL. I had the same issue, but got it resolved by syncing up the namespaces.

    In my case I had multiple bindings WsHttpBinding and WebHttpBinding, had missed the bindingNamespace in the config for WebHttpBinding

    ReplyDelete