Reputation: 135
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
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:
SoapReflectionImporter.IncludeType(Type)
SoapReflectionImporter.IncludeTypes(ICustomAttributeProvider)
.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