Solving the "disappearing data" issue when using Add Web Reference or Wsdl.exe with WCF services

A lot of people are reporting “mysterious” problems when using the Visual Studio “Add Web Reference” functionality or the wsdl.exe tool to consume WCF service. Let’s say you have the following service operation:

            [OperationContract]

            int GetShipmentQuote (Route r);

The operation is using the following data contract:

            [DataContract]

public class Route

{

            [DataMember]

            public int zipCodeFrom;

            [DataMember]

            public int zipCodeTo;

}

If you use “Add Web Reference” with this service, you will get a proxy along with a “Route” class. You may be tempted to use the proxy like this:

            Route r = new Route();

            r.zipCodeFrom = 98052;

            r.zipCodeTo = 98125;

            int price = proxy.GetShipmentQuote(r);

However, this won’t work – the zipCodeFrom and zipCodeTo items will be zero on the service side! If you look at the messages going over the wire, you will see that these items aren’t even transmitted from the client to the service. To discover the source of the problem, look at the generated Route class on the client side. It will look similar to the following (I am simplifying the class for clarity):

            public class Route

            {

                        public int zipCodeFrom { /* get/set code omitted… */ };

                        public bool zipCodeFromSpecified;

                        public int zipCodeTo { /* get/set code omitted… */ };

                        public bool zipCodeToSpecified;

                        //more code omitted…

            }

Where did these extra boolean “specified” members come from and what do they do? The answer is the schema that the WCF data contract serializer generates by default. Because of the way its versioning model works, the serializer generates all data members as optional elements. The older web services stack, ASP.NET Web Services (“ASMX”), uses a different serializer, the XmlSerializer, which maintains full schema and XML fidelity. The XmlSerializer maps all optional elements to two members: one represents the data itself, and one specifies whether or not the data is actually present – this is the “xxxSpecified” member. These xxxSpecified members must be set to true to enable the serialization of the corresponding “actual data” members. So, the correct way to use the proxy is as follows:

            Route r = new Route();

            r.zipCodeFrom = 98052;

            r.zipCodeFromSpecified = true;

            r.zipCodeTo = 98125;

            r.zipCodeToSpecified = true;

            int price = proxy.GetShipmentQuote(r);

Now, even when you know about this, you may find it somewhat annoying to always have to write such code. Instead, you can prevent the xxxSpecified members from being generated by marking the data members as required instead of optional:

            [DataContract]

public class Route

{

            [DataMember(IsRequired=true)]

            public int zipCodeFrom;

            [DataMember(IsRequired=true)]

            public int zipCodeTo;

}

Keep in mind that WCF will then enforce the requirement constraint and will throw an exception if a required data member is missing. It is usually fine to mark all data members as required in a first version of a data contract. However, if you add a new data member (e.g. if we also want to add an “int shipmentWeight” to our contract in v2), it should not be marked as required – otherwise, v1 clients will not be able to talk to v2 services (or vice versa, depending on where the data contract is used).

Another possible solution is to change the generated proxy code on the client side. You could specify default values of “true” for the xxxSpecified members, or you could modify the property setter for each member (e.g. zipCodeFrom) to set the corresponding xxxSpecified member (e.g. zipCodeFromSpecified) to true whenever that property is set. There are many circumstances in which modifying the generated proxy code is unacceptable – e.g. when you expect the service to change and thus expect to have to regenerate the proxy code often. In this situation, you can take advantage of the “partial class” feature to add a helper method to do the property setting work.

Of course, the real solution is to use WCF on the client side as well – this will completely eliminate the problem. If your client application cannot depend on .NET Framework 3.0, this is not an option for you. However, if the Framework version dependency is not a problem, you are strongly encouraged to use WCF on the client side – not only will the xxxSpecified problem go away, but you will also get all of the other WCF benefits such as higher performance in many cases. To use WCF on the client side, use “Add Service Reference” instead of “Add Web Reference” – if you do not have this option in Visual Studio, you should install the Visual Studio extensions for WCF from https://www.microsoft.com/downloads/details.aspx?familyid=F54F5537-CC86-4BF5-AE44-F5A1E805680D&displaylang=en (this feature will be greatly improved in the upcoming “Orcas” release). If you are using command-line tools, use svcutil.exe instead of wsdl.exe to generate the client proxy.

It is also worth mentioning that the xxxSpecified issue occurs at the parameter level as well, but for a slightly different reason. For example, if you have an operation with parameters as follows:

            [OperationContract]

            int GetShipmentQuote (int zipCodeFrom, int zipCodeTo);

The “Add Web Reference” proxy will look similar to the following:

            int GetShipmentQuote(int zipCodeFrom, bool zipCodeFromSpecified, int zipCodeTo, bool zipCodeToSpecified);

When using the proxy, make sure you set the xxxSpecified parameters to true:

            int price = GetShipmentQuote(98052,true,98125,true);

Again, you can use the partial class feature to add an overload of GetShipmentQuote to the generated proxy class that only takes zipCodeFrom and zipCodeTo and automatically sets the xxxSpecified parameters to “true”. And, again, if your client side uses WCF, the issue will not occur.

The reason the problem occurs at the parameter level is because our testing showed that parameters have to be marked as optional for maximum interoperability with 3rd-party web service stacks. Unlike with data members, there is no simple way to mark a parameter as required – and this is by design. If you really need to avoid xxxSpecified with parameters, there is a workaround involving [MessageContract(IsWrapped=false)] containing a data contract with required data members – but, again, consider the interoperability impact before going down this path.

Finally, I want to mention one other “solution” to the xxxSpecified issue – switching to the XmlSerializer on the service side. I am mentioning this only for completeness – this is by no means an approach I would recommend. There are various possible reasons why you may have to choose the XmlSerializer over the DataContractSerializer, but this is not one of them. The fact that the generated proxy in ASMX is somewhat less usable than expected should not influence your serialization technology choice. You get many benefits from the DataContractSerializer such as improved performance, improved versioning story, improved integration with other technologies including ones we are introducing in “Orcas”, and much more.