2

A Java shop vendor we're collaborating with needs us (a .NET shop) to provide a RESTful service conforming to a WSDL they've sent us. There are several differences between the XML we're returning and what the vendor expects. Is my process fundamentally wrong, or is there just a difference in the way Java and .NET do serialisation?

My process:

The contract-first idea is new to me, but is covered well in posts like this. So armed with a little knowledge I've run the WSDL and associated .XSDs through SvcUtil.exe to generate C# (some relevant excerpts below), and I'm using these types as the return types for my service.

I've also tried generating the code using wsdl.exe and xsd.exe, but neither significantly changed the outcome.

The differences:

  1. Additional levels appear in my XML. In mine, there is sometimes an XML entity corresponding to the property name (e.g. "Code") and inside it there's an XML element corresponding to the type of the property (e.g. "CodeType"). The vendor is expecting only one level: "Code".
  2. Namespaces are different.
  3. What the vendor expects as an attribute ("codeValue") appears as an element.
  4. Null properties are included in my XML.

Results and code excerpts:

The vendor expects the service to return messages looking like this:

<?xml version="1.0" encoding="UTF-8"?>
<gms:GetClassificationResponse xmlns:gms="http://vendor.com/metadata/cms/services/integration/gms" xmlns:class="http://vendor.com/metadata/model/classification" xmlns:core="http://vendor.com/metadata/model/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://vendor.com/metadata/cms/services/integration/gms ../gms_services.xsd">
  <class:ClassificationVersion>
    <class:Code codeValue="01">
      <Name core:type="Preferred">Northland Region</Name>
      <class:Code codeValue="001">
        <Name core:type="Preferred">Far North District</Name>
        <class:Code codeValue="500206">
          <Name core:type="Preferred">North Cape</Name>
        </class:Code>
        <class:Code codeValue="500207">
          <Name core:type="Preferred">Houhora</Name>
        </class:Code>
        <!-- Truncated -->
      </class:Code>
      <class:Code codeValue="002">
        <Name core:type="Preferred">Whangarei District</Name>
        <!-- Children omitted -->
      </class:Code>
      <!-- Truncated -->
    </class:Code>
    <class:Code codeValue="02">
      <Name core:type="Preferred">Auckland Region</Name>
      <!-- Children omitted -->
    </class:Code>
    <!-- Truncated -->
  </class:ClassificationVersion>
</gms:GetClassificationResponse>

We're producing results like this:

<GetClassificationResponseType xmlns="http://schemas.datacontract.org/2004/07/metadata.cms.services.integration.gms" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ClassificationVersion>
    <actionField>ADD</actionField>
    <actionFieldSpecified>false</actionFieldSpecified>
    <anyAttrField xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml" i:nil="true"/>
    <descriptionField i:nil="true"/>
    <detailField>full</detailField>
    <idField i:nil="true"/>
    <itemsField xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true"/>
    <lastUpdateField>0001-01-01T00:00:00</lastUpdateField>
    <lastUpdateFieldSpecified>false</lastUpdateFieldSpecified>
    <nameField i:nil="true"/>
    <uriField i:nil="true"/>
    <isPreferredField>false</isPreferredField>
    <itemField i:nil="true"/>
    <validFromField>0001-01-01T00:00:00</validFromField>
    <validFromFieldSpecified>false</validFromFieldSpecified>
    <validToField>0001-01-01T00:00:00</validToField>
    <validToFieldSpecified>false</validToFieldSpecified>
    <Code>
      <CodeType>
        <actionField>ADD</actionField>
        <actionFieldSpecified>false</actionFieldSpecified>
        <anyAttrField xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml" i:nil="true"/>
        <descriptionField i:nil="true"/>
        <detailField>full</detailField>
        <idField>Whangarei</idField>
        <itemsField xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true"/>
        <lastUpdateField>0001-01-01T00:00:00</lastUpdateField>
        <lastUpdateFieldSpecified>false</lastUpdateFieldSpecified>
        <nameField>
          <ContextualStringType>
            <anyField/>
            <isStructuredField>true</isStructuredField>
            <actionField>ADD</actionField>
            <actionFieldSpecified>false</actionFieldSpecified>
            <anyAttrField xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml" i:nil="true"/>
            <langField i:nil="true"/>
            <typeField>Preferred</typeField>
          </ContextualStringType>
        </nameField>
        <uriField i:nil="true"/>
        <Code>
          <CodeType>
            <actionField>ADD</actionField>
            <actionFieldSpecified>false</actionFieldSpecified>
            <anyAttrField xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml" i:nil="true"/>
            <descriptionField i:nil="true"/>
            <detailField>full</detailField>
            <idField>Springs Flat</idField>
            <itemsField xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true"/>
            <lastUpdateField>0001-01-01T00:00:00</lastUpdateField>
            <lastUpdateFieldSpecified>false</lastUpdateFieldSpecified>
            <nameField>
              <ContextualStringType>
                <anyField/>
                <isStructuredField>true</isStructuredField>
                <actionField>ADD</actionField>
                <actionFieldSpecified>false</actionFieldSpecified>
                <anyAttrField xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml" i:nil="true"/>
                <langField i:nil="true"/>
                <typeField>Preferred</typeField>
              </ContextualStringType>
            </nameField>
            <uriField i:nil="true"/>
            <Code/>
            <category i:nil="true"/>
            <codeValue>502001</codeValue>
            <level xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true"/>
          </CodeType>

And some of the generated code

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://mtna.us/metadata/cms/services/integration/gms")]
[DataContract]
public partial class GetClassificationResponseType
{
    private ClassificationVersionType classificationVersionField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://mtna.us/metadata/model/classification", Order = 0)]
    [DataMember]
    public ClassificationVersionType ClassificationVersion
    {
        get
        {
            return this.classificationVersionField;
        }
        set
        {
            this.classificationVersionField = value;
        }
    }
}

...

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://vendor.com/metadata/model/classification")]
public partial class ClassificationVersionType : ObjectVersionType
{

    private LevelType1[] levelField;

    private CodeType[] codeField;

    private bool isViewField;

    private string basisField;

    public ClassificationVersionType()
    {
        this.isViewField = false;
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("Level", Order = 0)]
    public LevelType1[] Level
    {
        get
        {
            return this.levelField;
        }
        set
        {
            this.levelField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("Code", Order = 1)]
    public CodeType[] Code
    {
        get
        {
            return this.codeField;
        }
        set
        {
            this.codeField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    [System.ComponentModel.DefaultValueAttribute(false)]
    public bool isView
    {
        get
        {
            return this.isViewField;
        }
        set
        {
            this.isViewField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute(DataType = "anyURI")]
    public string basis
    {
        get
        {
            return this.basisField;
        }
        set
        {
            this.basisField = value;
        }
    }
}
Community
  • 1
  • 1
OutstandingBill
  • 2,614
  • 26
  • 38
  • Were you able to get this working? – Dakotah Hicock May 08 '15 at 20:58
  • Eventually, yes. I've since left that organisation, and my memory of the solution is a bit sketchy I'm afraid, but I'll do my best to help you out. The solution involved two main tricky bits. First - writing code to be used higher up the call stack than your usual service code. Second, tweaking the classes generated by SvcUtil. There were some much nicer ways to solve this problem (e.g. the vendor write an intermediary service) but our vendor was intransigent. – OutstandingBill May 10 '15 at 00:42
  • Thank you so much! I'm still working out some solutions. Our vendor is being the same. – Dakotah Hicock May 10 '15 at 01:10
  • why not JSON ? final json of .net and java has to be the same , so just implementation will change which i doubt will be a issue – Srinath Ganesh May 10 '15 at 01:50
  • The vendor was pretty firm about wanting XML – OutstandingBill May 10 '15 at 02:01

1 Answers1

1

We eventually got this working, with a lot of help from the vendor. I've since left the organisation - but I kept a copy of the code.

We used this - https://msdn.microsoft.com/en-us/library/aa395223(v=vs.110).aspx - our full implementation of which follows below.

We decorated the service interface with DispatchByBodyElementBehavior:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[ServiceContract(Namespace = "http://vendor.com/project/wsdl"), XmlSerializerFormat, DispatchByBodyElementBehavior]
public interface IContractFirstService

We also had to modify the code generated by SvcUtil.exe, but not massively. There were just a few places where things needed tweaking. Something to do with array limits. Sorry, I can't remember the detail. I'm pretty sure it was fairly obvious what to do from the compile/run-time errors.

Here's how we implemented DispatchByBodyElementBehavior:

using System;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.Collections.Generic;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;

/*
 * This code was written based on the sample provided at http://msdn.microsoft.com/en-us/library/aa395223(v=vs.110).aspx. 
 * The purpose of this is to allow dispatching of the incoming SOAP requests based on the payload XML element. 
 * This allows the more standard document/literal service contract design to be used and does not rely on SOAP actions. 
 */
namespace OurOrganisation.Services.VendorIntegrationServices
{
    class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
    {
        Dictionary<XmlQualifiedName, string> dispatchDictionary;
        string defaultOperationName;

        public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName, string> dispatchDictionary, string defaultOperationName)
        {
            try
            {
                this.dispatchDictionary = dispatchDictionary;
                this.defaultOperationName = defaultOperationName;
            }
            catch (Exception ex)
            {
                Logger.Write(ex.Message);
                throw;
            }
        }

        #region IDispatchOperationSelector Members

        private Message CreateMessageCopy(Message message, XmlDictionaryReader body)
        {
            Message copy = Message.CreateMessage(message.Version, message.Headers.Action, body);
            copy.Headers.CopyHeaderFrom(message, 0);
            copy.Properties.CopyProperties(message.Properties);
            return copy;
        }

        public string SelectOperation(ref System.ServiceModel.Channels.Message message)
        {
            try
            {
                XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
                XmlQualifiedName lookupQName = new XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
                message = CreateMessageCopy(message, bodyReader);
                if (dispatchDictionary.ContainsKey(lookupQName))
                {
                    return dispatchDictionary[lookupQName];
                }
                else
                {
                    return defaultOperationName;
                }
            }
            catch (Exception ex)
            {
                Logger.Write(ex.Message);
                throw;
            }

        }

        #endregion
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
    sealed class DispatchByBodyElementBehaviorAttribute : Attribute, IContractBehavior
    {
        #region IContractBehavior Members

        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            // no binding parameters need to be set here
            return;
        }

        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            // this is a dispatch-side behavior which doesn't require
            // any action on the client
            return;
        }

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
        {
            // We iterate over the operation descriptions in the contract and
            // try to locate an DispatchBodyElementAttribute behaviors on each 
            // operation. If found, we add the operation, keyed by QName of the body element 
            // that selects which calls shall be dispatched to this operation to a 
            // dictionary. 

            //Logger.Write("starting ApplyDispatchBehavior");

            try
            {
                Dictionary<XmlQualifiedName, string> dispatchDictionary = new Dictionary<XmlQualifiedName, string>();
                foreach (OperationDescription operationDescription in contractDescription.Operations)
                {
                    DispatchBodyElementAttribute dispatchBodyElement =
                        operationDescription.Behaviors.Find<DispatchBodyElementAttribute>();
                    if (dispatchBodyElement != null)
                    {
                        dispatchDictionary.Add(dispatchBodyElement.QName, operationDescription.Name);
                        //Logger.Write(string.Format("method {0} added", operationDescription.Name));
                    }
                }

                // Lastly, we create and assign and instance of our operation selector that
                // gets the dispatch dictionary we've just created.
                dispatchRuntime.OperationSelector =
                    new DispatchByBodyElementOperationSelector(
                        dispatchDictionary,
                        dispatchRuntime.UnhandledDispatchOperation.Name);
            }
            catch(Exception ex)
            {
                Logger.Write(ex.Message);
                throw;
            }
        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
            // 
        }

        #endregion
    }

    [AttributeUsage(AttributeTargets.Method)]
    sealed class DispatchBodyElementAttribute : Attribute, IOperationBehavior
    {
        XmlQualifiedName qname;

        public DispatchBodyElementAttribute(string name)
        {
            qname = new XmlQualifiedName(name);
        }

        public DispatchBodyElementAttribute(string name, string ns)
        {
            qname = new XmlQualifiedName(name, ns);
        }

        internal XmlQualifiedName QName
        {
            get { return qname; }
            set { qname = value; }
        }


        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation)
        {

        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
        {

        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        #endregion
    }

    internal class Logger
    {
        public static void Write(string message)
        {
            string sSource;
            string sLog;
            string sEvent;

            sSource = "MAL dispatch by body extension";
            sLog = "Application";
            sEvent = message;

            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);

            EventLog.WriteEntry(sSource, sEvent);
        }
    }
}
OutstandingBill
  • 2,614
  • 26
  • 38
  • Can you share what's the changes you have done in the class generated by svcutil. – Kamran Shahid Dec 15 '15 at 07:06
  • 1
    @Kamran I'm sorry, I can't because I no longer have the original svcutil-generated file to compare against. From memory the changes were ones I was forced to make because of compile-time or run-time errors, and they were quite easy to resolve using Google. – OutstandingBill Dec 15 '15 at 21:39
  • No problem Bill. We eventually needed to change even internal of the proxy class. And then it work – Kamran Shahid Dec 16 '15 at 07:41