0

To consume a SOAP service, I need to send messages in XML which looks like:

<soap:Envelope 
    xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:bus="http://ws.praxedo.com/v6/businessEvent">
    
    <soap:Header/>
    <soap:Body>
        <bus:listAttachments>
                <businessEventId>00044</businessEventId>
        </bus:listAttachments>
    </soap:Body>
</soap:Envelope>

Obviously, the easy way to do this would just be to create a string and interpolate some variable (e.g. businessEventId) into the midst of it, like:

$"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
+ " xmlns:bus=\"http://ws.praxedo.com/v6/businessEvent\">"
+ "<soap:Header/><soap:Body>"
+ "<bus:listAttachments><businessEventId>{businessEventId}</businessEventId>"
+ "</bus:listAttachments></soap:Body></soap:Envelope>"

But I'd rather instantiate some collection of POPO, like:

public class Envelope {
   public Header Header {get; init;}
   public Body Body {get; init;}
}

public class Header {
}

public class Body {
   public ListAttachments Attachments {get; init;}
}

public class ListAttachments {
   public string BusinessEventId {get; init;}
}

What Attributes do I need to declare?
And what would I then need to do to serialize this, assuming I have a populated instance already?

--

As per @DavidBrowne's comment, I've tried the following which does NOT work:

        private string CreateBody(string businessEventId)
        {
            BusinessEventAttachmentListRequestEnvelope envelope = BusinessEventAttachmentListRequestEnvelope.From(businessEventId);

            MemoryStream memorystream = new();
            DataContractSerializer serializer = new(typeof(BusinessEventAttachmentListRequestEnvelope));
            serializer.WriteObject(memorystream, envelope);

            memorystream.Seek(0, SeekOrigin.Begin);

            using StreamReader streamReader = new(memorystream);
            return streamReader.ReadToEnd();
        }

    [DataContract(Name = "Envelope", Namespace = "http://www.w3.org/2003/05/soap-envelope")]
    public class BusinessEventAttachmentListRequestEnvelope
    {
        [DataMember]
        EmptySoapHeader Header = new();

        [DataMember]
        BusinessEventAttachmentListRequestBody Body { get; init; }

        public static BusinessEventAttachmentListRequestEnvelope From(string businessEventId) =>
            new()
            {
                Body = new()
                {
                    Request = new()
                    {
                        BusinessEventId = businessEventId
                    }
                }
            };
    }

    [DataContract(Name = "Header", Namespace = "http://www.w3.org/2003/05/soap-envelope")]
    public class EmptySoapHeader
    {
    }

    [DataContract(Name = "Body", Namespace = "http://www.w3.org/2003/05/soap-envelope")]
    public class BusinessEventAttachmentListRequestBody
    {
        [DataMember(Name = "listAttachments")]
        public BusinessEventAttachmentListRequest Request { get; init; }
    }

    [DataContract(Name = "listAttachments", Namespace = "http://ws.praxedo.com/v6/businessEvent")]
    public class BusinessEventAttachmentListRequest
    {
        [DataMember(Name = "businessEventId")]
        public string BusinessEventId { get; init; }
    }

The resulting XML seems substantially different:

<Envelope xmlns=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
    <Body>
        <listAttachments xmlns:a=\"http://ws.praxedo.com/v6/businessEvent\">
            <a:businessEventId>00044</a:businessEventId>
        </listAttachments>
    </Body>
    <Header/>
</Envelope>

And the remote server returns error 500.

Brian Kessler
  • 2,187
  • 6
  • 28
  • 58
  • 1
    It depends on which serializer you're using. Here's for the DataContractSerializer: https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/datacontractserializer-sample – David Browne - Microsoft Sep 21 '21 at 12:48
  • @DavidBrowne-Microsoft, Thanks for the fast response. Let's say I don't care what serializer I use, as long as it works with .Net 5.0 and gets the job done properly. But what I do care about is that I can get all those fancy element names, complete with colons and those attributes on the root. – Brian Kessler Sep 21 '21 at 12:51
  • 1
    You just configure namespace for each element. The serializer will add the namespace declarations, and it doesn't matter exactly how they are aliased. – David Browne - Microsoft Sep 21 '21 at 12:54
  • @DavidBrowne-Microsoft, Thanks again for the response. I'm not 100% clear what you mean... I also wouldn't place any high level of confidence in anything not mattering to the server which will receive this... Even if something shouldn't matter, there is no guarantee their implementation is correct or likely to be corrected. – Brian Kessler Sep 21 '21 at 13:02
  • 1
    It's basic XML that the namespace alias doesn't matter. So long as the server uses a real XML parser, it should work fine. But some serializers give you control over this. The older XmlSerializer is more complicated, but provides more control. See eg: https://stackoverflow.com/questions/2339782/xml-serialization-and-namespace-prefixes – David Browne - Microsoft Sep 21 '21 at 13:43
  • @DavidBrowne-Microsoft, cheers for the responses, but I think I'm a bit too much of a noobie at C#/SOAP/XML to figure out how to make this high level information work... You can see my attempt with DataContractSerializer above. My attempt with XmlSerializerNamespaces was much less productive.... – Brian Kessler Sep 21 '21 at 14:02

1 Answers1

2

If you're using Visual Studio there's a really easy shortcut you can use: Paste XML as Classes:

enter image description here

Which creates for your sample XML:

// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
        /// <remarks/>
        [System.SerializableAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.w3.org/2003/05/soap-envelope")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.w3.org/2003/05/soap-envelope", IsNullable = false)]
        public partial class Envelope
        {

            private object headerField;

            private EnvelopeBody bodyField;

            /// <remarks/>
            public object Header
            {
                get
                {
                    return this.headerField;
                }
                set
                {
                    this.headerField = value;
                }
            }

            /// <remarks/>
            public EnvelopeBody Body
            {
                get
                {
                    return this.bodyField;
                }
                set
                {
                    this.bodyField = value;
                }
            }
        }

        /// <remarks/>
        [System.SerializableAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.w3.org/2003/05/soap-envelope")]
        public partial class EnvelopeBody
        {

            private listAttachments listAttachmentsField;

            /// <remarks/>
            [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://ws.praxedo.com/v6/businessEvent")]
            public listAttachments listAttachments
            {
                get
                {
                    return this.listAttachmentsField;
                }
                set
                {
                    this.listAttachmentsField = value;
                }
            }
        }

        /// <remarks/>
        [System.SerializableAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://ws.praxedo.com/v6/businessEvent")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://ws.praxedo.com/v6/businessEvent", IsNullable = false)]
        public partial class listAttachments
        {

            private byte businessEventIdField;

            /// <remarks/>
            [System.Xml.Serialization.XmlElementAttribute(Namespace = "")]
            public byte businessEventId
            {
                get
                {
                    return this.businessEventIdField;
                }
                set
                {
                    this.businessEventIdField = value;
                }
            }
        }
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Browne, cheers for this... I will try it. For completeness, once I've created these models, do I need to use any particular serializer to get these models to work as expected? – Brian Kessler Sep 21 '21 at 14:56
  • I was able to get this working using `System.Xml.XmlSerializer`. Cheers! – Brian Kessler Sep 21 '21 at 15:29