Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
306 views
in Technique[技术] by (71.8m points)

c# - 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">
            &#60;?xml version=&#34;1.0&#34;?&#62;&#60;lipsum
            xmlns:xsi=&#34;http://www.w3.org/2001/XMLSchema-instance&#34;
            xmlns:xsd=&#34;http://www.w3.org/2001/XMLSchema&#34;&#62;&#60;
            foobar foo=&#34;Lorem&#34; bar=&#34;Ipsum&#34;/&#62;&#60;/lipsum&#62;
        </SOAP-ENC:string>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...