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>
XmlSerializer
doesn't need[Serializable]
or[DataMember]
. I'll read the question too, honest :) (edit: interesting; don't know!) – ZuberSoapFormatter
but it can't handle generic types (I'm updating the sample with that snippet). – Gatling[Serializable]
but I'll keep the[DataMember]
as the same class might or might not be serialized to JSON and XML viaDataContractSerializer
andDataContractJsonSerializer
respectively. – Gatlinglipsum.Foobar.Add
doesn't compile. – CanareseType 'Namespace.Of.Lipsum' in Assembly 'Namespace.Of.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
– Gatling