Extension method to serialize generic objects as a SOAP formatted stream
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>
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.
精彩评论