Fasten your seatbelts, you are in for the next round of Web Reference vs. Service Reference, which brings an unexpected twist to the story. After giving reasons why not to use Web References, I’ll now put my devil’s advocate’s hat on, and try to have you change your mind.
It’s simple: there are situations where Service Reference won’t work as expected, and Web Service will.
This time, you don’t need to create new objects, you can simply run what’s already there in NAV. Just publish the page 7002 Sales Prices as a Web service, then call ReadMultiple on that Web service both through a Web Reference and a Service Reference.
Okay – before you do that – you’ll want to delete (or change) the name of the Currency Code Filter control – it can’t have the same name as the Sales Type Filter Control – Web services won’t allow that (why does RTC not complain about this, beats me royally).
Also, if any of the calls returned no data, which is what a virgin Cronus database would do, feed some sales prices into it first, then call ReadMultiple.
Now observe the results. The Web Reference code actually returned all the data as expected. The Service Reference is funny: every row returned will simply have the Key property populated with correct data, all other fields will simply be either null (if nullable) or their default value (if not nullable). Unbelievable, isn’t it?
Looking at the XML passed on by NAV as a SOAP response, you can see that it actually contains all the data, just as expected. So what is it, then?
Figuring this one out is not as difficult as it might seem at first. Of course, the problem is not in NAV (actually, there is a problem in NAV, but not that NAV doesn’t return the correct info, it’s something else, that I’ll talk about later) – the problem is actually in how Web Reference and Service Reference deserialize the XML.
To understand how each of them do that, you need to check the source code of the generated proxy class. You can do it either by
a) right-clicking the proxy class name in any place in the code where it is used and choosing Go To Definition (or Go To Declaration, if you are using Resharper);
b) by clicking the Show All Files in Solution Explorer
then locating the reference source (always called Reference.cs):
The Reference.cs file contains all the classes needed for handling the SalesPrice Web service, and inspecting the SalesPrice class itself, which is the class representing the NAV Sales Price record, inspecting the serialization attributes will reveal the culprit in a split second:
Do you see what I see?
It’s the Order=x parameter of the XmlElementAttribute. An innocent looking thing, yet it causes such a disaster in deserialization of the data. What it does is that it basically instructs the XML deserializer to only continue deserializing the XML as long as the order of elements in the SOAP message is correct. If elements come out of order, deserialization doesn’t fail, but it simply ignores all the elements that come at, or after the position, of the first element which violated the order.
The Order parameter, as well as all of the serialization attributes, are present in the Service Reference proxy classes, but not present in Web Reference ones. That simply means that deserialization of the SOAP message will not take the order of the elements into account for Web References, whereas it will do so for the Service References.
Which in fact means there must be something wrong with the SOAP response. Why doesn’t it return the elements in the required order?
To figure that out, let’s inspect the message, and compare it to the WSDL definition of the Web service.
This is what WSDL defines:
And this is what the SOAP response contains (I use Fiddler to catch stuff like this):
And there you go. Since Key property did deserialize well, and since the proxy class said it needed it at position 0, s’il vous plait, then it must be whatever came at position 1 that made the deserialization give up.
See it? The SalesTypeFilter element, which is present in the SOAP response, is nowhere to be seen in the WSDL definition. All other elements come in order (please note that Order will only make the deserialization fail if elements are out of order, or extra elements are present – it won’t fail if an element is not present, but the elements that are present are in order).
Why this SalesTypeFilter found its way into SOAP, but was too shy to show up in WSDL, is a mystery to me, so I attribute it to a bug in Web services. And since it’s the Web services that returned that element, you can’t do much about it.
So, is this a dead-end, then? I mean, you must use Service Reference because it can fail at reading XML representation of unprintable characters, and you can’t use Service Reference because it can fail at ordering at deserialization.
Fortunately, it’s not that bad.
The workaround here is simple. You simply take out all Order parameters of every single XmlElementAttribute attribute (yes, you can edit the Reference.cs and modify the definition of a proxy class!), and all of a sudden the Service Reference deserializes the SOAP as happily as any Web Reference.
There are two problems with this solution: first, you absolutely must not update the Service Reference, because it would stick those Order parameters back where they were, and second, you must do this for every single class if you want to be sure it will work. But hey – at least you can work around this.
As much as Web services as a technology are consumer-agnostic, it seems that consumers themselves are pretty religious about what they do. Two different consumption approaches in .NET, and both behave in specific ways, causing you trouble if you don’t pay attention.
Which one would I use? Service Reference, any day. Getting rid of the Order parameter is as simple as this regular expression: (s*Order=d+s*,?s*)|(,?s*Order=d+s*) (thanks, same to you, too!) (and yes – please feel free to point out how bat it is or to improve it, it was just impromptued here, I didn’t make any attempt to optimize it)
… and an Appendix
Talking about SOAP, let’s make a soap opera here. I won’t end this Web Reference vs. Service Reference here. I’ve given you two very practical arguments about what’s wrong and what’s not about these two approaches. I’m going to write another post, rather scholarly, where I’ll theorize on some less obvious aspect of a major difference between those two approaches, which actually affects anything that you do, if you want to use generic Web service approach, rather than specifically referencing every Web service you intend to use.
See you soon.