简体   繁体   中英

Java serialization vs .NET serialization

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 (eg "Code") and inside it there's an XML element corresponding to the type of the property (eg "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;
        }
    }
}

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);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM