Thursday, December 21, 2006

Consume WCF BasicHttpBinding Services from ASMX Clients

The interoperability of WCF services has been touted quite a bit: just expose an endpoint using BasicHttpBinding and you will have a WS-I Basic Profile adherent web-service. This is of course backed by some simple examples of math services, all RPC-style and none of them messaging-style services.

We have for some time been consuming our BasicHttpBinding WCF services from a Flex 2 client and a WinForms smart client using a WCF generated client proxy (svcutil.exe). Just to be certain that our partners would have no problem setting up an application-to-application integration; I decided to test our services ASMX-style using "Add web reference" in Visual Studio (wsdl.exe). This to ensure that the services are consumable from non-WCF systems, i.e. systems without the .NET 3 run-time installed.

Well, surprise, surprise; it wasn't as straightforward as shown in the examples. There are several details that you need to be aware of, and some bugs in the service/message/data contract serializing mechanism.

I started by creating a new WinForms application and just used "Add web reference" to reference my "projectdocumentservices.svc?WSDL" file and WSDL.EXE generated an ASMX-style proxy for me. So far this looks good. I then added code to call my HelloWorld method on the proxy, which gave me this run-time error:

System.InvalidOperationException: Method ProjectDocumentServices.GetDocumentRequirement can not be reflected. ---> System.InvalidOperationException: The XML element 'KeyCriteriaMessage' from namespace 'http://DNVS.DNVX.eApproval.ServiceContracts/2006/11' references a method and a type. Change the method's message name using WebMethodAttribute or change the type's root element using the XmlRootAttribute.

Note that the Flex 2 client has no such problems with the BasicHttpBinding web-service, thus it must be related to how the generated proxy interprets the WSDL.

My service uses the specified message in several operations:

[System.ServiceModel.OperationContractAttribute(Action = "GetDocumentCard")]DocumentCardResponse GetDocumentCard(KeyCriteriaMessage request);

[System.ServiceModel.OperationContractAttribute(Action = "GetDocumentCategory")]DocumentCategoryResponse GetDocumentCategory(KeyCriteriaMessage request);

[System.ServiceModel.OperationContractAttribute(Action = "GetDocumentRequirement")]DocumentRequirementResponse GetDocumentRequirement(KeyCriteriaMessage request);

I turned to MSDN for more info and found the article 'ASMX Client with a WCF Service' (note the RPC-style math services) which lead me to 'Using the XmlSerializer'. So, accordingly, I added the [XmlSerializerFormat] attribute to the [ServiceContract] attribute in my service interface. Then I compiled and inspected the service, and got a series of different errors when trying to view the generated WSDL:

[SEHException (0x80004005): External component has thrown an exception.]

[CustomAttributeFormatException: Binary format of the specified custom attribute was invalid.]

[InvalidOperationException: There was an error reflecting type 'DNVS.DNVX.eApproval.DataContracts.DocumentCard'.]

Applying the well-known XML serialization attributes [Serializable] and [XmlIgnore] at relevant places in the data and message contracts helped me to isolate the problem to the use of collections and the WCF [CollectionDataContract] attribute.

To make a long story short, there is a bug in the WCF framework that affects .asmx but not .svc when using the [CollectionDataContract(Name="...")] attribute parameter:

"This happens only with Asmx pages but not for Svc due to a known Whidbey issue. Removing the "Name" parameter setting would work. If it is possible, you could also have your client instead use svc only."

Removing the Name parameter from all my [CollectionDataContract] attributes made all the WSDL reflection errors disappear, and I was again able to view the generated WSDL file. This time for a service that used the [XmlSerializerFormat] attribute.

Full of hope, I updated the web reference in my test client and ran it. Calling the test method lead me straight back to my original problem: the message contract used in multiple operations. Note that removing all but one of the operations makes the problem go away.

According to 'Message WSDL Considerations' WCF should have done this when generating the WSDL:

"When using the same message contract in multiple operations, multiple message types are generated in the WSDL document. The names are made unique by adding the numbers "2", "3", and so on for subsequent uses. When importing back the WSDL, multiple message contract types are created and are identical except for their names."

Inspecting the WSDL showed me that multiple XSD schemas are not generated. Certainly another WCF issue/bug. The problem can be solved by deriving the message class into new classes with unique names and using the derived classes in the operations. But when you have a message that is used ubiquitously, e.g. DefaultOperationResponse, this is not a viable solution.

As of now, our BasicHttpBinding WCF service cannot be consumed by ASMX clients. So much for interoperability...

[UPDATE] Read my new post about how to expose a WCF service also as an ASMX web-service.

1 comment:

nateOne said...

I'm not having the exact same issues, but I'm glad I'm not the only one for whom "basicHttpBinding" did not work "out of the box".

Thanks for your post - we're making an ASMX front end for the web service at this moment.