locked
XmlSerializer and SOAP/RPC encoding RRS feed

  • Question

  • Hi,

    Some help is desperately needed...

    I need to provide a solution for serializing/deserializing SOAP requests in both literal and encoded styles.

    For literal style I use XmlSerializer and found no difficulties at all, but when it came to encoded style (e.g. RPC) I was surprised to figure out that XmlSerializer couldn't generate a valid object.

    For example, I'd like XmlSerializer to deserialize the following XML/Soap Body:

    <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

        <tns:EchoBooleanArray>

            <blBoolean href="#id1" />

        </tns:EchoBooleanArray>

        <soapenc:Array id="id1" soapenc:arrayType="xsd:boolean[5]">

            <Item>true</Item>

            <Item>true</Item>

            <Item>false</Item>

            <Item>false</Item>

            <Item>true</Item>

        </soapenc:Array>

    </soap:Body>

    I'd expect to receive some object of the following type:

    public class EchoBooleanArray

    {

        public boolean[] blBoolean;

    };

    But I receive an object containing an empty array instead of an array containing 5 booleans. From my point of view the XmlSerializer did not went to the destination of the href attribute

    I use .NET 1.1.4322.573 version with WSE 2.0.5050.0 SP3.

    I tried to use SoapEnvelope.GetObjectBody() method, but have got the same behaviour.

    I tried to move to SoapFormatter, but found that it has problems deresializing literal style and WSE Soap headers related to addressing.

    Wednesday, June 7, 2006 8:53 AM

Answers

  • Hello Uros,

    Thanks for the reply.

    I can't understand how the proposed attributes should be used in deserialization.

    I don't need to use the webmethods and webservice calls. I just need to take the received SOAP envelope and deserialize called method to an object. For that, I find the relevant method and its arguments in the WSDL and define a type which contains the called method arguments. then using this type I try to deserialize the SOAP body.

    I do add [XmlType(Namespace=http://myNamespace)] with the namespace of the called method.

    Actually the types I define look as the following:

    namespace My.Namespace
    {
          [XmlType(Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
          public class Body
          {
                // Methods
                public Body();

                // Fields
                public EchoBooleanArray EchoBooleanArray;
          }

          [XmlType(Namespace=http://myNamespace)]
          public class EchoBooleanArray
          {
                // Methods
                public EchoBooleanArray();

                // Fields
                public bool[] blBoolean;
          }
    }

    And I expect the Soap envelope (from the first post) to be deserialized to an object of the above type, but unfortunately the created object of the "EchoBooleanArray" type contains an uninitialized feild "blBoolean" of type bool[].

    The two following variants lead to the generated object look the same:

    Variant 1:

    Type reqType = GetCalledMethodType();

    Object obj = m_SoapEnvelope.GetBodyObject( reqType, GetCalledMethodNamespace() );

    Variant 2:

    XmlRootAttribute xRoot = new XmlRootAttribute();

    xRoot.ElementName = m_SoapEnvelope.Body.LocalName;

    xRoot.Namespace = m_SoapEnvelope.Body.NamespaceURI;

    XmlSerializer ser = new XmlSerializer( reqType, xRoot );

    obj = ser.Deserialize( new XmlNodeReader( m_SoapEnvelope.Body ) );

     

    Vitali

    Wednesday, June 7, 2006 2:50 PM

All replies

  • Hello Vitali,

    I think that you need to apply appropriate attributes to your webmethods or webservice, that allow you to serialize your messages in RPC-Encoded style.

    These attributes are SoapRpcMethodAttribute for webmethods and SoapRpcServiceAttribute for webservice. They are located at System.Web.Services.Description namespace.

    Regrads,

    Uros

    Wednesday, June 7, 2006 1:28 PM
  • Hello Uros,

    Thanks for the reply.

    I can't understand how the proposed attributes should be used in deserialization.

    I don't need to use the webmethods and webservice calls. I just need to take the received SOAP envelope and deserialize called method to an object. For that, I find the relevant method and its arguments in the WSDL and define a type which contains the called method arguments. then using this type I try to deserialize the SOAP body.

    I do add [XmlType(Namespace=http://myNamespace)] with the namespace of the called method.

    Actually the types I define look as the following:

    namespace My.Namespace
    {
          [XmlType(Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
          public class Body
          {
                // Methods
                public Body();

                // Fields
                public EchoBooleanArray EchoBooleanArray;
          }

          [XmlType(Namespace=http://myNamespace)]
          public class EchoBooleanArray
          {
                // Methods
                public EchoBooleanArray();

                // Fields
                public bool[] blBoolean;
          }
    }

    And I expect the Soap envelope (from the first post) to be deserialized to an object of the above type, but unfortunately the created object of the "EchoBooleanArray" type contains an uninitialized feild "blBoolean" of type bool[].

    The two following variants lead to the generated object look the same:

    Variant 1:

    Type reqType = GetCalledMethodType();

    Object obj = m_SoapEnvelope.GetBodyObject( reqType, GetCalledMethodNamespace() );

    Variant 2:

    XmlRootAttribute xRoot = new XmlRootAttribute();

    xRoot.ElementName = m_SoapEnvelope.Body.LocalName;

    xRoot.Namespace = m_SoapEnvelope.Body.NamespaceURI;

    XmlSerializer ser = new XmlSerializer( reqType, xRoot );

    obj = ser.Deserialize( new XmlNodeReader( m_SoapEnvelope.Body ) );

     

    Vitali

    Wednesday, June 7, 2006 2:50 PM
  • You cannot use XmlSerialzier class to Serialize/Deserialize rpc/encoded style messages.

    You can use XmlSerializer framework to create a SoapSerializer, here is a sample:

     

        using System.Reflection;

        using System.Collections;

        using System.IO;

        using System.Xml.Schema;

        using System.Xml;

        using System.Xml.Serialization;

        using System;

     

        public class SoapSerializer {

            internal const string SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";

            internal const string SoapEncoding = "http://schemas.xmlsoap.org/soap/encoding/";

            internal const string SoapEnvelope = "Envelope";

            internal const string SoapBody = "Body";

            internal const string SoapEncodingStyle = "encodingStyle";

     

            internal XmlSerializer serializer;

     

            public SoapSerializer(Type type, SoapAttributeOverrides overrides, Type[] extraTypes, string defaultNamespace) {

     

                SoapReflectionImporter importer = new SoapReflectionImporter(overrides, defaultNamespace);

                for (int i = 0; i < extraTypes.Length; i++) {

                    importer.IncludeType(extraTypes);

                }

                XmlTypeMapping mapp = importer.ImportTypeMapping(type);

                serializer = new XmlSerializer(mapp);

            }

     

            public SoapSerializer(Type type, Type[] extraTypes) : this(type, null, extraTypes, null) {

            }

     

            public SoapSerializer(Type type, SoapAttributeOverrides overrides) : this(type, overrides, new Type[0], null) {

            }

     

            public SoapSerializer(Type type) : this(type, null, new Type[0], null) {

            }

     

            public SoapSerializer(Type type, string defaultNamespace)  : this(type, null, new Type[0], defaultNamespace) {

            }

     

            public void Serialize(TextWriter textWriter, object o) {

                Serialize(textWriter, o, null);

            }

     

            public void Serialize(TextWriter textWriter, object o, XmlSerializerNamespaces namespaces) {

                XmlTextWriter xmlWriter = new XmlTextWriter(textWriter);

                xmlWriter.Formatting = Formatting.Indented;

                xmlWriter.Indentation = 2;

                Serialize(xmlWriter, o, namespaces);

            }

     

            public void Serialize(Stream stream, object o) {

                Serialize(stream, o, null);

            }

           

            public void Serialize(Stream stream, object o, XmlSerializerNamespaces namespaces) {

                XmlTextWriter xmlWriter = new XmlTextWriter(stream, null);

                xmlWriter.Formatting = Formatting.Indented;

                xmlWriter.Indentation = 2;

                Serialize(xmlWriter, o, namespaces);

            }

     

            public void Serialize(XmlWriter xmlWriter, object o) {

                Serialize(xmlWriter, o, null);

            }

     

            public void Serialize(XmlWriter xmlWriter, object o, XmlSerializerNamespaces namespaces) {

                xmlWriter.WriteStartDocument();

     

                if (namespaces != null && namespaces.Count > 0) {

                    xmlWriter.WriteStartElement(SoapEnvelope, SoapNamespace);

                    XmlQualifiedName[] xmlns = namespaces.ToArray();

                    for (int i = 0; i < xmlns.Length; i++) {

                        string prefix = xmlns.Name;

                        string ns = xmlns.Namespace;

                        string oldPrefix = (ns == null || ns.Length == 0) ? "" : xmlWriter.LookupPrefix(ns);

     

                        if (oldPrefix == null || oldPrefix != prefix) {

                            xmlWriter.WriteAttributeString("xmlns", prefix, null, ns);

                        }

                    }

                }

                else {

                    xmlWriter.WriteStartElement("soap", SoapEnvelope, SoapNamespace);

                    xmlWriter.WriteAttributeString("xmlns", "soap", null, SoapNamespace);

                    xmlWriter.WriteAttributeString("xmlns", "soapenc", null, SoapEncoding);

                    xmlWriter.WriteAttributeString("xmlns", "xsi", null, XmlSchema.InstanceNamespace);

                    xmlWriter.WriteAttributeString("xmlns", "xsd", null, XmlSchema.Namespace);

                }

                xmlWriter.WriteStartElement(SoapBody, SoapNamespace);

                if (namespaces == null) {

                    xmlWriter.WriteAttributeString("soap", SoapEncodingStyle, null, SoapEncoding);

                }

                serializer.Serialize(xmlWriter, o, namespaces);

     

                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();

                xmlWriter.Flush();

            }

           

            public object Deserialize(Stream stream) {

                XmlTextReader xmlReader = new XmlTextReader(stream);

                xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;

                xmlReader.Normalization = true;

                xmlReader.XmlResolver = null;

                return Deserialize(xmlReader);

            }

           

            public object Deserialize(TextReader textReader) {

                XmlTextReader xmlReader = new XmlTextReader(textReader);

                xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;

                xmlReader.Normalization = true;

                xmlReader.XmlResolver = null;

                return Deserialize(xmlReader);

            }

           

            public object Deserialize(XmlReader xmlReader) {

                xmlReader.ReadStartElement(SoapEnvelope, SoapNamespace);

                xmlReader.ReadStartElement(SoapBody, SoapNamespace);

                object o = serializer.Deserialize(xmlReader);

                xmlReader.ReadEndElement();

                xmlReader.ReadEndElement();

     

                return o;

            }

        }

     

    Keep in mind that for the rpc/encoded style messages you need to use different custom attributes:

    SoapAttributeAttribute

    SoapElementAttribute

    SoapEnumAttribute

    SoapIgnoreAttribute

    SoapIncludeAttribute

    SoapTypeAttribute

    And that the rpc/encoded messages are type-based.

     

    So if you need to change the qname of the first child of the soap:Body element, you need to use [SoapType] attribute (not [XmlRoot]).

     

    Thanks,

    Elena Kharitidi

    Thursday, June 8, 2006 12:24 AM
    Moderator
  • Elena,

    Thank you very much. This was very helpful and knowledgeable.

    I've got it working now. I tried to use SoapTypeAttribute, but somehow I used it from

    System.Runtime.Remoting.Metadata namespace. I also didn't use the whole envelope in the stream when gave it to the Deserialize method. I also used the called method part of the XML to initialize the XmlReader. I guess the XmlReader needs to start from the beginning of the envelope.

    So thank you, Elena.

    Thursday, June 8, 2006 8:00 AM
  • Hi, can somebody give me an example/demo of how to use the suggested class .

    Thanks in advance

    Wednesday, August 29, 2007 4:36 PM