Deserializing detail of SOAP message

13,663

Heavily inspired by How to Deserialize XML document, I think this should do the trick.

I generated the class as follows:

  1. Save the Xml server reply to disk (c:\temp\reply.xml)
  2. xsd c:\temp\reply.xml /o:"c:\temp"
  3. xsd c:\temp\reply.xsd reply_app1.xsd /classes /o:"c:\temp"
  4. Add reply_app1.cs to your project.

This resulted in:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.269
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System.Xml.Serialization;

// 
// This source code was auto-generated by xsd, Version=4.0.30319.1.
// 

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://schemas.xmlsoap.org/soap/envelope/", IsNullable=false)]
public partial class Envelope {

    private EnvelopeBody[] itemsField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("Body")]
    public EnvelopeBody[] Items {
        get {
            return this.itemsField;
        }
        set {
            this.itemsField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
public partial class EnvelopeBody {

    private EnvelopeBodyFault[] faultField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("Fault")]
    public EnvelopeBodyFault[] Fault {
        get {
            return this.faultField;
        }
        set {
            this.faultField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
public partial class EnvelopeBodyFault {

    private string faultcodeField;

    private string faultstringField;

    private EnvelopeBodyFaultDetail detailField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string faultcode {
        get {
            return this.faultcodeField;
        }
        set {
            this.faultcodeField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string faultstring {
        get {
            return this.faultstringField;
        }
        set {
            this.faultstringField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public EnvelopeBodyFaultDetail detail {
        get {
            return this.detailField;
        }
        set {
            this.detailField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.xmlsoap.org/soap/envelope/")]
public partial class EnvelopeBodyFaultDetail {

    private InnerException innerExceptionField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Namespace="http://my.namespace.com")]
    public InnerException InnerException {
        get {
            return this.innerExceptionField;
        }
        set {
            this.innerExceptionField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://my.namespace.com")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://my.namespace.com", IsNullable=false)]
public partial class InnerException {

    private string messageField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string message {
        get {
            return this.messageField;
        }
        set {
            this.messageField = value;
        }
    }
}

Yes, it is auto generated, but I figured that would be the easiest way to create a class that you would be able to deserialize. Which you can do like this:

XmlDocument document = new XmlDocument();
document.Load(Server.MapPath("~/Data/reply.xml"));

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Envelope));
Envelope envelope = (Envelope)serializer.Deserialize(new StringReader(document.OuterXml));

Which correctly picked up the Detail property. In your case you would need to use the XmlReader like you were doing before instead of my new StringReader(document.OuterXml), and wrap it in a using block, etc.

Keep in mind that some of the property names of the deserialized object won't match up exactly with what you had in your SoapFault class (it's now called Envelope, for example), and some of the properties are expressed as arrays rather than single objects, but you could probably tweak the Xsd to suit if need be.

Share:
13,663
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin about 2 years

    I am working on a Windows Store application.

    I have the following manually created Fault class:

    [XmlRoot(Namespace = "http://schemas.xmlsoap.org/soap/envelope/", ElementName = "Fault")]
    public partial class SoapFault
    {
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "faultcode")]
        public String FaultCode { get; set; }
    
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "faultstring")]
        public String FaultDescription { get; set; }
    
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "detail")]
        public InnerException[] Detail { get; set; }
    }
    
        [XmlType(Namespace = "http://my.namespace.com", TypeName = "InnerException")]
        public partial class InnerException 
        {
            [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "message")]
            public String Message { get; set; }
        }
    

    This is the reply of the server i am trying to read:

    <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><env:Fault><faultcode>env:Server</faultcode><faultstring>internal error</faultstring><detail><ns2:InnerException xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://ns1.my.namespace.com" xmlns:ns2="http://my.namespace.com" xmlns:ns3="http://ns3.my.namespace.com"><message>internal error</message></ns2:InnerException ></detail></env:Fault></env:Body></env:Envelope>
    

    The way i am trying to read it is as follows:

    using (XmlReader reader = XmlReader.Create(await response.Content.ReadAsStreamAsync()))
    {
        string SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
        try
        {
             var serializer = new XmlSerializer(typeof(SoapFault));
    
             reader.ReadStartElement("Envelope", SoapNamespace);
             reader.ReadStartElement("Body", SoapNamespace);
    
             var fault = serializer.Deserialize(reader) as SoapFault;
    
             reader.ReadEndElement();
             reader.ReadEndElement();
        }
        catch(Exception ex)
        {
             throw new Exception("Exception was thrown:" + ex.Message);
        }
    }
    

    I have tried adding namespaces, changing the XmlElement Attributes, but i always end up with the Detail property in the SoapFault being NULL. When i change the type to object, i at least get a XmlNode set which contains the data.

    What must i change in this code to get the correct class instantiated on serialization?

    Please note: I am unfortunatly forced to create the calls by hand and cannot use auto-generated code.

  • mungflesh
    mungflesh over 9 years
    for step #3 above, if step #2 generated more than 2 files, you might need to list them all as arguments to xsd, ie. xsd reply.xsd reply_app1.xsd reply_app2.xsd ... etc
  • Yiping
    Yiping about 7 years
    You are a life-saver