Search code examples
c#web-serviceswcfsoapestes-express-api

ESTES ShipmentTracking v1.1 Web Service, Error in deserializing body of reply message for operation 'shipmentTracking'


I am trying to develop a simple Windows Forms app to request tracking information from the latest version ESTES ShipmentTracking v1.1 web service. When I execute the Request, my program throws a System.ServiceModel.CommunicationException

I am using Visual Studio 2019 and .NET 4.6.2 C# Windows Forms application. I configured a Connected Service using the Add Service Reference procedure and use the ESTES_Track namespace. Here is my essential code:

ESTES_Track.EstesShipmentTracking_PortTypeClient trackClient = new EstesShipmentTracking_PortTypeClient();
trackClient.ClientCredentials.UserName.UserName = "MYUSERNAME";
trackClient.ClientCredentials.UserName.Password = "MYPASSWORD";
ESTES_Track.search trackSearch = new ESTES_Track.search();
trackSearch.requestID = "TRACK" + DateTime.Now.Ticks.ToString();
trackSearch.pro = "1710394802";
ESTES_Track.shipmentTrackingRequest trackRequest = new shipmentTrackingRequest(trackSearch);
ESTES_Track.shipmentTrackingResponse trackResponse = trackClient.shipmentTracking(trackRequest);

Communication Exception is:

Error in deserializing body of reply message for operation 'shipmentTracking'
There is an error in XML document (1, 575)
   at System.ServiceModel.Dispatcher.XmlSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, XmlSerializer serializer, MessagePartDescription returnPart, MessagePartDescriptionCollection bodyParts, Object[] parameters, Boolean isRequest)
   at System.ServiceModel.Dispatcher.XmlSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)
   at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
   at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeReply(Message message, Object[] parameters)
   at System.ServiceModel.Dispatcher.ProxyOperationRuntime.AfterReply(ProxyRpc& rpc)
   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at EstesTests.ESTES_Track.EstesShipmentTracking_PortType.shipmentTracking(shipmentTrackingRequest request)
   at EstesTests.ESTES_Track.EstesShipmentTracking_PortTypeClient.shipmentTracking(shipmentTrackingRequest request) in C:\tests\EstesTests\Connected Services\ESTES_Track\Reference.cs:line 1394
   at EstesTests.Program.TrackTest() in C:\tests\EstesTests\Program.cs:line 49

-=-=-=- edit -=-=-=- I can successfully process a Request and get a valid Response using SoapUI. This leads me to believe my problem is specific to my Visual Studio project.

My App.config file looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="estesrtshipmenttracking_base_ws_provider_soapws_EstesShipmentTracking_Binder">
                  <security mode="Transport">
                    <transport clientCredentialType="Basic"></transport>
                  </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://api.estes-express.com:443/ws/estesrtshipmenttracking.base.ws.provider.soapws:EstesShipmentTracking/estesrtshipmenttracking_base_ws_provider_soapws_EstesShipmentTracking_Port"
                binding="basicHttpBinding" bindingConfiguration="estesrtshipmenttracking_base_ws_provider_soapws_EstesShipmentTracking_Binder"
                contract="ESTES_Track.EstesShipmentTracking_PortType" name="estesrtshipmenttracking_base_ws_provider_soapws_EstesShipmentTracking_Port" />
        </client>
    </system.serviceModel>
</configuration>

-=-=-=- edit -=-=-=- The reason I explicitly labeled this as "ESTES ShipmentTracking v1.1" is that I am quite sure the problem(s) I am having are likely specific to this particular web service. If someone already has a working code sample for using this web service, That might include the solution to my problem. Also, I am sure anyone else trying to develop a shipping client for ESTES will encounter this problem.

-=-=-=- conclusion -=-=-=- I conclude that the XML content of the response messages does not adequately comply with the published v1.1 WSDL schema and that the deserialization exceptions are specific to this web service implementation version. I have not experienced this problem with the previous v1.0 version.


Solution

  • I experienced the same problem today and narrowed it down to a couple issues:

    • The eventTimeStamp is missing a colon in the timezone portion
    • Empty fields are missing xsi:nil

    I wrote to tech support, but in the meanwhile was able to get it to work by manipulating the response with a custom message inspector as described here https://blogs.msdn.microsoft.com/dsnotes/2015/04/14/wcf-simple-way-to-modify-serialized-response/.

    You can add it to your client like this:

    trackClient.Endpoint.Behaviors.Add(new EstesTrackingEndpointBehavior());
    

    Hopefully they can address this on their end. If other issues come up, you can add additional changes to the response in AfterReceiveReply:

    using System;
    using System.IO;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.Xml;
    
    namespace EstesWebService {
        public class EstesTrackingMessageInspector : IClientMessageInspector {
            public void AfterReceiveReply(ref Message reply, object correlationState) {
                var doc = new XmlDocument();
                var ms = new MemoryStream(); 
                var writer = XmlWriter.Create(ms);
                reply.WriteMessage(writer);
                writer.Flush();
                ms.Position = 0;
                doc.Load(ms);
    
                //fix the XML
                addNil(doc.SelectNodes(".//shipments"));
                foreach (XmlNode node in doc.SelectNodes(".//eventTimeStamp"))
                    fixDateTimeFormat(node);
    
                ms.SetLength(0);
                writer = XmlWriter.Create(ms);
                doc.WriteTo(writer);
                writer.Flush();
                ms.Position = 0;
                var reader = XmlReader.Create(ms);
                reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
            }
    
            public object BeforeSendRequest(ref Message request, IClientChannel channel) {
                return null;
            }
    
            private void addNil(XmlNodeList nodes) {
                foreach (XmlNode node in nodes) {
                    if (node.HasChildNodes)
                        addNil(node.ChildNodes);
                    else if (string.IsNullOrWhiteSpace(node.InnerText) && node.Attributes != null && node.Attributes.GetNamedItem("xsi:nil") == null) {
                        var attr = node.OwnerDocument.CreateAttribute("xsi", "nil", "http://www.w3.org/2001/XMLSchema-instance");
                        attr.Value = "true";
                        node.Attributes.SetNamedItem(attr);
                    }
                }
            }
    
            private void fixDateTimeFormat(XmlNode node) {
                if (node != null && !string.IsNullOrWhiteSpace(node.InnerText)) {
                    DateTimeOffset dt;
                    if (DateTimeOffset.TryParse(node.InnerText.Trim(), out dt))
                        node.InnerText = dt.ToString("O");
                }
            }
    
        }
    
        public class EstesTrackingEndpointBehavior : IEndpointBehavior {
            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {
            }
    
            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
                clientRuntime.MessageInspectors.Add(new EstesTrackingMessageInspector());
            }
    
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {
            }
    
            public void Validate(ServiceEndpoint endpoint) {
            }
        }
    }