views:

144

answers:

2

I'm struggling with migrating from webservice/webclient architecture to WCF architecture. The object are very complex, with lots of nested xsd's and different namespaces. Proxy classes are generated by adding a Web Reference to an original wsdl with 30+ webmethods and using xsd.exe for generating the missing SOAPFault objects. My pilot WCF Service consists of only 1 webmethod which matches the exact syntax of one of the original methods: 1 object as parameter, returning 1 other object as result value. I greated a WCF Interface using those proxy classes, using attributes: XMLSerializerFormat and ServiceContract on the interface, OperationContract on one method from original wsdl specifying Action, ReplyAction, all with the proper namespaces. I create incoming client messages using SoapUI; I generated a project from the original WSDL files (causing the SoapUI project to have 30+ methods) and created one new Request at the one implemented WebMethod, changed the url to my wcf webservice and send the message. Because of the specified (Reply-)Action in the OperationContractAttribute, the message is actually received and properly deserialized into an object. To get this far (40 hours of googling), a lot of frustration led me to using a custom endpoint in which the WCF 'wrapped tags' are removed, the namespaces for nested types are corrected, and the generated wsdl get's flattened (for better compatibility with other tools then MS VisualStudio).

Interface code is this:

[XmlSerializerFormat(Use = OperationFormatUse.Literal, Style = OperationFormatStyle.Document, SupportFaults = true)]
[ServiceContract(Namespace = Constants.NamespaceStufZKN)]
public interface IOntvangAsynchroon
{

    [OperationContract(Action = Constants.NamespaceStufZKN + "/zakLk01", ReplyAction = Constants.NamespaceStufZKN + "/zakLk01", Name = "zakLk01")]
    [FaultContract(typeof(Fo03Bericht), Namespace = Constants.NamespaceStuf)]
    Bv03Bericht zakLk01([XmlElement("zakLk01", Namespace = Constants.NamespaceStufZKN)] ZAKLk01 zakLk011);

When I use a Webclient in code to send a message, everything works. My problem is, when I use a WCF client. I use ChannelFactory< IOntvangAsynchroon > to send a message. But the generated xml looks different: it includes the parametername of the method! It took me a lot of time to figure this one out, but here's what happens:

Correct xml (stripped soap envelope):

<soap:Body>
<zakLk01 xmlns="http://www.egem.nl/StUF/sector/zkn/0310"&gt;
<stuurgegevens>
<berichtcode xmlns="http://www.egem.nl/StUF/StUF0301"&gt;Bv01&lt;/berichtcode&gt;
<zender xmlns="http://www.egem.nl/StUF/StUF0301"&gt;
<applicatie>ONBEKEND</applicatie>
</zender>
</stuurgegevens>
<parameters>
</parameters>
</zakLk01>
</soap:Body>

Bad xml:

<soap:Body>
<zakLk01 xmlns="http://www.egem.nl/StUF/sector/zkn/0310"&gt;
<zakLk011>
<stuurgegevens>
<berichtcode xmlns="http://www.egem.nl/StUF/StUF0301"&gt;Bv01&lt;/berichtcode&gt;
<zender xmlns="http://www.egem.nl/StUF/StUF0301"&gt;
<applicatie>ONBEKEND</applicatie>
</zender>
</stuurgegevens>
<parameters>
</parameters>
</zakLk011>
</zakLk01>
</soap:Body>

Notice the 'zakLk011' element? It is the name of the parameter of the method in my interface! So NOW it is zakLk011, but it when my parameter name was 'zakLk01', the xml seemed to contain some magical duplicate of the tag above, but without namespace. Of course, you can imagine me going crazy over what was happening before finding out it was the parametername!

I now have actually created a WCF Service, at which I cannot send messages using a WCF Client anymore. For clarity: The method does get invoked using the WCF Client on my webservice, but the parameter object is empty. Because I'm using a custom endpoint to log the incoming xml, I can see the message is received fine, but just with the wrong syntax!

WCF client code:

ZAKLk01 stufbericht = MessageFactory.CreateZAKLk01();
ChannelFactory<IOntvangAsynchroon> factory = new ChannelFactory<IOntvangAsynchroon>(new BasicHttpBinding(), new EndpointAddress("http://localhost:8193/Roxit/Link/zkn0310"));
factory.Endpoint.Behaviors.Add(new LinkEndpointBehavior());
IOntvangAsynchroon client = factory.CreateChannel();
client.zakLk01(stufbericht);

I am not using a generated client, i just reference the webservice (share libraries) like I am used to.

Edit: When generating a Service Reference, it generates duplicate classes(dont know why..). When these duplicates are removed however, a client sends messages with correct xml. But my architecture requires shared libraries, so this doesn't help me out.

Can anyone please help me? I can't google anything on this...

A: 

Suggestion: if you're just getting started with WCF, then start off by doing things "the WCF way". Once you know how to do that correctly, you can start changing things. Right now, you don't know how much of your problem is due to WCF, and how much is just due to your lack of experience with WCF.

I suggest you start from scratch, not using [XmlSerializerFormat]. Just create a ServiceContract with a single OperationContract. Include at least one FaultContract so you can see how that works.

Create a WCF client using "Add Service Reference", and make sure it works.

Then create a SOAPUI project using the WSDL from the WCF service (not the original WSDL). Make sure that works.

Then you can start changing things. Try [XmlSerializerFormat]. Try adding various [Xml*] attributes. Slowly start changing things until you see what breaks.

John Saunders
A: 

Okay, I figured it out myself. I had already created a WCF Client (Service Reference) that worked, and by looking closely at the generated code, I figured out what is happening. It has to do with way WCF wrappes all classes used in the DECLARATION of Webservice methods (so the classes used in properties of the method-mentioned-classes are not wrapped). In my case, the body of the ZAKLk01 class get wrapped with XMLElement tags, using the parametername as XMLElement-name. To get rid of this behaviour I am now using wrapper classes for my generated proxy-classes ZAKLk01 and Bv03Bericht, just like the generated classes do of my Service References proxy classes in my new WCF Client. These wrapper classes are decorated with MessageContractAttributes.

To give an example of one of those wrapper classes:

[MessageContract(IsWrapped = false)]
public partial class zakLk01Request
{

    [MessageBodyMember(Namespace = Constants.NamespaceStufZKN, Order = 0)]
    public ZAKLk01 zakLk01;

    public zakLk01Request()
    {
    }

    public zakLk01Request(ZAKLk01 zakLk01)
    {
        this.zakLk01 = zakLk01;
    }
}

My Interface method now looks like this:

[OperationContract(Action = Constants.NamespaceStufZKN + "/zakLk01", ReplyAction = Constants.NamespaceStufZKN + "/Bv03Bericht")]
[FaultContract(typeof(Fo03Bericht), Namespace = Constants.NamespaceStuf)]
zakLk01Response zakLk01(zakLk01Request zakLk01);

Much cleaner without the XMLElement tags, which function (generate correct xml) have now been replaced by the wrapper classes.

The reason I could receive non-wrapped xml, was that my custom messageinspector contained some code, meant to accept non-wrapped xml messages without having to add MessageContract tags to all existing classes (or create lots of wrapper classes) (googled it somewhere), which it did just fine. Code snippet:

MessageDescription.Body.WrapperName = null;

But receiving wrapped messages which were send by my (first) WCF Client which still wrapped the classes didn't work, off course...

What I still don't understand is how those Action attributes work: if I don't supply them, my generated wsdl does not contain any method. Well, not important for now, since I can finally move on, and will tinker with the Action attributes some other time.

Wouter