Danil.T
Danil.T

Reputation: 135

How to serialize a c# object with polymorphism to a soapmessage with overriden soapattributes, similar to XmlAttributeOverrides?

I have a dynamically created type, so I can't change it's implementation. I can't apply solutions like

[SoapInclude()] 

Imagine if I had such classes:

public class BaseClass{}
public class DerivedClass:BaseClass{}
public class AnotherDerivedClass:BaseClass{}

public class RequestClass
{
    public BaseClass item{get;set;}
}

If we try to just simply serialize we get the exception that DerivedClass was not found, so we have to override the xmlattributes. I have managed to override my Type and serialize it with XmlAttributeOverrides like so:

var requestObject = new RequestClass() {item = new DerivedClass()};
XmlAttributes attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute
{
    ElementName = "DerivedClass",
    Type = typeof(DerivedClass)
};
attrs.XmlElements.Add(attr);
XmlAttributeOverrides attoverrides = new XmlAttributeOverrides();
attoverrides.Add(typeof(RequestClass), "item", attrs);
XmlSerializer sr = new XmlSerializer(typeof(RequestClass), attoverrides);
StringWriter writer = new StringWriter();
sr.Serialize(writer, requestObject);
var xml = writer.ToString();

Now the above works, but what I want is to serialize my object as a soap-message. I have found similar classes to those above Like SoapAttributeOverrides and I've tried to override it with those classes and it doesn't seem to work. I've tried it like :

var requestObject = new RequestClass(){item = new DerivedClass()};
SoapAttributeOverrides ovr = new SoapAttributeOverrides();
SoapAttributes soapAtts = new SoapAttributes();
SoapElementAttribute element = new SoapElementAttribute();
element.ElementName = "DerivedClass";
ovr.Add(typeof(RequestClass), "item", soapAtts);
var sr = new XmlSerializer(new SoapReflectionImporter(ovr).ImportTypeMapping(typeof(RequestClass)));
StringWriter writer = new StringWriter();
sr.Serialize(writer, requestObject);
var xml = writer.ToString();
writer.Close();

I again get the exception DerivedClass was not found. How can I give the SoapElementAttribute a type similar to XmlElementAttribute.Type so that serialization can be made to support polymorphism for BaseClass via overrides?

Upvotes: 2

Views: 311

Answers (1)

dbc
dbc

Reputation: 117086

SOAP-encoded XML serialization in .Net supports polymorphism via an included type mechanism. In order to add included types to a SoapReflectionImporter in runtime, use either:

Thus you can manufacture your XmlSerializer as follows:

public static XmlSerializer RequestClassSerializer { get; } 
    = CreateSoapSerializerWithBaseClassIncludedTypes(typeof(RequestClass), typeof(DerivedClass), typeof(AnotherDerivedClass));

static XmlSerializer CreateSoapSerializerWithBaseClassIncludedTypes(Type rootType, params Type [] includedTypes)
{
    var importer = new SoapReflectionImporter();
    foreach (var type in includedTypes)
        importer.IncludeType(type);
    return new XmlSerializer(importer.ImportTypeMapping(rootType));
}

Then introduce the following extension method:

public static partial class XmlSerializationHelper
{
    public static string GetSoapXml<T>(this T obj, XName wrapperName, XmlSerializer serializer = null)
    {
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                xmlWriter.WriteStartElement(wrapperName.LocalName, wrapperName.NamespaceName);
                (serializer ?? GetDefaultSoapSerializer(obj.GetType())).Serialize(xmlWriter, obj);
                xmlWriter.WriteEndElement();
            }
            return textWriter.ToString();
        }
    }

    static readonly Dictionary<Type, XmlSerializer> cache = new Dictionary<Type, XmlSerializer>();

    public static XmlSerializer GetDefaultSoapSerializer(Type type)
    {
        lock(cache)
        {
            if (cache.TryGetValue(type, out var serializer))
                return serializer;
            return cache[type] = new XmlSerializer(new SoapReflectionImporter().ImportTypeMapping(type));
        }
    }
}

And you will be able to serialize your requestObject as follows:

var requestObject = new RequestClass(){item = new DerivedClass()};

var xml = requestObject.GetSoapXml("wrapper", RequestClassSerializer);

Notes:

  • To avoid a severe memory leak you should statically cache any XmlSerializer not created with the XmlSerializer(Type) or XmlSerializer(Type, String) constructors. For why, see the documentation and also Memory Leak using StreamReader and XmlSerializer.

    If you are dynamically generating families of types you may need to cache their serializers in a static dictionary protected by lock statements.

  • As explained in Extension method to serialize generic objects as a SOAP formatted stream and InvalidOperationException while SOAP serialization of complex type you need to manually write an envelope or other wrapper element before serializing directly to XML using a SOAP XML serializer.

    If for some reason you just want the SOAP XML body as a series of XML fragments, you can do:

    public static string GetSoapBodyXml<T>(this T obj, XmlSerializer serializer = null)
    {
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true, ConformanceLevel = ConformanceLevel.Fragment }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                xmlWriter.WriteWhitespace(""); // Hack to prevent an error about WriteStartDocument getting called for ConformanceLevel.Fragment
                (serializer ?? GetDefaultSoapSerializer(obj.GetType())).Serialize(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }
    

Demo fiddle here.

Upvotes: 2

Related Questions