I'm having a hard time trying to figure out a generic extension method that would serialize a given object as SOAP formatted. The actual implementation looks somewhat like this:
Foobar.cs
[Serializable, XmlRoot("foobar"), DataContract]
public class Foobar
{
[XmlAttribute("foo"), DataMember]
public string Foo { get; set; }
[XmlAttribute("bar"), DataMember]
public string Bar { get; set; }
public Foobar() {}
}
Lipsum.cs
[Serializable, XmlRoot("lipsum"), XmlType("lipsum"), DataContract]
public class Lipsum
{
private List<Foobar> lipsum = new List<Foobar>();
[XmlElement("foobar"), DataMember]
public List<Foobar> Lipsum { get { return lipsum; } }
}
}
Extensions.cs
public static void SerializeToSoap<T>(this Stream target, T source)
{
XmlTypeMapping xmlTypeMapping = (new SoapReflectionImporter().ImportTypeMapping(typeof(T)));
XmlSerializer xmlSerializer = new XmlSerializer(xmlTypeMapping);
xmlSerializer.Serialize(target, source);
}
Program.cs
static void Main()
{
Lipsum lipsum = new Lipsum();
lipsum.Lipsum.Add(
new Foobar()
{
Foo = "Lorem",
Bar = "Ipsum"
}
);
using (MemoryStream persistence = new MemoryStream())
{
persistence.SerializeToSoap<Lipsum>(lipsum);
Console.WriteLine(Encoding.Default.GetString(persistence.ToArray()));
Console.WriteLine(Environment.NewLine);
}
}
EXCEPTION
System.InvalidOperationException: Token StartElement in state Epilog would result in an invalid XML document.
at System.Xml.XmlTextWriter.AutoComplete(Token token)
at System.Xml.XmlTextWriter.WriteStartElement(String prefix, String localName, String ns)
at System.Xml.Serialization.XmlSerializationWriter.WriteStartElement(String name, String ns, Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns)
at System.Xml.Serialization.XmlSerializationWriter.WriteArray(String name, String ns, Object o, Type type)
at System.Xml.Serialization.XmlSerializationWriter.WriteReferencedElement(String name, String ns, Object o, Type ambientType)
at System.Xml.Serialization.XmlSerializationWriter.WriteReferencedElements()
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.
Write4_Lipsum(Object o)
On the other hand both XML and JSON serialization (using XmlSerializer
and DataContractJsonSerializer
respectively) is working fine with the following expected results:
<?xml version="1.0"?>
<lipsum xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<foobar foo="Lorem" bar="Ipsum" />
</lipsum>
{"Lipsum":[{"Foo":"Lorem","Bar":"Ipsum"}]}
Any advice will be sincerely appreciated. Thanks much in advance.
UPDATE
As one of the commenters remarked, there's a SoapFormatter
class but given that I was aware it can't deal with generic types haven't included that snippet. So anyway this would be the code for that scenario:
public static void SerializeToSoap<T>(this Stream target, T source)
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.Serialize(target, source);
}
Which throws the following exception:
Exception caught: Soap Serializer does not support serializing Generic Types : System.Collections.Generic.List`1[Foobar].
UPDATE 2
Following the lead given by Merlyn Morgan-Graham I've been tried to feed the SoapFormatter
with a non-generic object, so after a little juggling with MemoryStream
I've ended up with this:
using (MemoryStream xmlStream = new MemoryStream())
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(xmlStream, lipsum);
using (MemoryStream soapStream = new MemoryStream())
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.Serialize(soapStream, Encoding.Default.GetString(xmlStream.ToArray()));
Console.WriteLine(Encoding.Default.GetString(soapStream.ToArray()));
Console.WriteLine(Environment.NewLine);
}
}
Which surprisingly enough, character entities aside, outputs a decent SOAP message:
<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<SOAP-ENC:string id="ref-1">
<?xml version="1.0"?><lipsum
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"><
foobar foo="Lorem" bar="Ipsum"/></lipsum>
</SOAP-ENC:string>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The simplest thing to do is mark your objects with the Serializable attribute and then use a binary formatter to handle the serialization. The entire class graph shouldn't be a problem provided that any contained objects are also marked as Serializable.
In SOAP and BINARY serialization technique the state of the entire object is serialized into a stream of bytes. In cases where the object contains a reference to other objects, even those are serialized. This type of serialization is known as deep serialization.
As with the CreatePo method, you must first construct an XmlSerializer, passing the type of class to be deserialized to the constructor. Also, a FileStream is required to read the XML document. To deserialize the objects, call the Deserialize method with the FileStream as an argument.
I (half) solved the problem by wrapping the serialization with another element, ala this forum post: http://forums.asp.net/p/1510998/3607468.aspx, and this blog post: http://sandblogaspnet.blogspot.com/2009/07/serialization-in-net-3.html
public static void SerializeToSoap<T>(this Stream target, T source)
{
XmlTypeMapping xmlTypeMapping = (new SoapReflectionImporter().ImportTypeMapping(typeof(T)));
XmlSerializer xmlSerializer = new XmlSerializer(xmlTypeMapping);
using (var xmlWriter = new XmlTextWriter(target, Encoding.UTF8))
{
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("root");
xmlSerializer.Serialize(xmlWriter, source);
xmlWriter.WriteFullEndElement();
}
}
The document looks really strange, though, and doesn't contain a SOAP Envelope. Admittedly I know very little about SOAP, so maybe you know how to solve those problems :)
Edit:
Looking at reflected source in System.Web.Services.Protocols.SoapHttpClientProtocol
, it looks like the SoapReflectionImporter
and XmlSerializer
are used, but the SOAP envelope and body are generated directly within the serialization code. No special helpers are exposed to the user. So you'll probably have to wrap that part of the message with custom code.
On the plus side, the code looks fairly simple - just write the correct elements with the proper namespaces/attributes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With