I have two classes, shown below:
[Serializable]
[XmlInclude(typeof(SomeDerived))]
public class SomeBase
{
public string SomeProperty { get; set; }
}
public class SomeDerived : SomeBase
{
[XmlIgnore]
public new string SomeProperty { get; set; }
}
When I serialize and instance of SomeDerived I don't expect to see a value for SomeProperty. However, I do. I've tried other approaches such as having SomeProperty declared as virtual in SomeBase and overriding it in SomeDerived. Still I see it in a serialized instance of SomeDerived.
Can anyone explain what is going on with the XmlIgnoreAttribute?
For completeness, my deserialization code is below
class Program
{
static void Main(string[] args)
{
SomeDerived someDerived = new SomeDerived { SomeProperty = "foo" };
XmlSerializer ser = new XmlSerializer(typeof(SomeBase));
MemoryStream memStream = new MemoryStream();
XmlTextWriter xmlWriter = new XmlTextWriter(memStream, Encoding.Default);
ser.Serialize(memStream, someDerived);
xmlWriter.Close();
memStream.Close();
string xml = Encoding.Default.GetString(memStream.GetBuffer());
Console.WriteLine(xml);
Console.ReadLine();
}
}
Edit
I get the same behaviour if I change the serializer declaration to new XmlSerializer(typeof(SomeDerived)).
Try this out. It uses the override on the XmlSerializer constructor to pass in some serialization overrides:
SomeDerived someDerived = new SomeDerived { SomeProperty = "foo" };
// Create the XmlAttributeOverrides and XmlAttributes objects.
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attrs = new XmlAttributes();
/* Use the XmlIgnore to instruct the XmlSerializer to ignore
the GroupName instead. */
attrs = new XmlAttributes();
attrs.XmlIgnore = true;
overrides.Add(typeof(SomeBase), "SomeProperty", attrs);
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), overrides);
MemoryStream memStream = new MemoryStream();
XmlTextWriter xmlWriter = new XmlTextWriter(memStream, Encoding.Default);
ser.Serialize(memStream, someDerived);
xmlWriter.Close();
memStream.Close();
string xml = Encoding.Default.GetString(memStream.GetBuffer());
Related
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?
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.
[Serializable]
class GameObject : PictureBox
{
public bool Solid;
public bool Selected;
}
Is there any way to serialize BackColor, Size, Location etc...?
For temporary small type of objects I prefer to use structures, although you can use classes as per your needs. You can serialize and de-serialize objects using these helper methods.
public static string Serialize<T>(T objectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
StringWriter textWriter = new StringWriter();
xmlSerializer.Serialize(textWriter, objectToSerialize);
return textWriter.ToString();
}
public static T Deserialize<T>(string stringToDeserialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
StringReader textReader = new StringReader(stringToDeserialize);
return (T)xmlSerializer.Deserialize(textReader);
}
How to Serialize object-to-string:
MyStructure myObject = GetPictureBoxObject();
string pbSerializedString = Serialize<MyStructure>(myObject);
How to DeSerialize string-to-object:
string str = GetStringToDeserialize();
MyStructure myObject = Deserialize<MyStructure>(str);
I have these classes which I use:
namespace defaultNamespace
{
...
public class DataModel
{
}
public class Report01
{get; set;}
public class Report02
{get; set;}
}
And I have a method that creates the XML below.
public XmlDocument ObjectToXml(object response, string OutputPath)
{
Type type = response.GetType();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(type);
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
serializer.Serialize(writer, response);
XmlDocument xmldoc = new XmlDocument();
stream.Position = 0;
StreamReader sReader = new StreamReader(stream);
xmldoc.Load(sReader);
stream.Position = 0;
string tmpPath = OutputPath;
while (File.Exists(tmpPath))
{
File.Delete(tmpPath);
}
xmldoc.Save(tmpPath);
return xmldoc;
}
And I have two lists that has a Report01 and Report02 object.
List<object> objs = new List<object>();
List<object> objs2 = new List<object>();
Report01 obj = new Report01();
obj.prop1 = "aa";
obj.prop2 = "bb";
objs.Add(obj);
Report02 obj2 = new Report02();
obj2.prop1 = "cc";
obj2.prop2 = "dd";
objs2.Add(obj2);
When I try to create the XML like this:
ObjectToXml(objs, "c:\\12\\objs.xml");
ObjectToXml(objs2, "c:\\12\\objs2.xml");
I see this exception:
The type "Report01(or Report02)" was not expected. Use the XmlInclude
or SoapInclude attribute to specify types that are not known
statically.
How can I solve this problem?
It's because your response.GetType() actually returns List<object> type and then you tried to serialize non expected type. Object don't know anything about your types and serializer for Object can't serialize your unknown types.
You can use BaseClass for your reports and XmlInclude to solve this exception:
[XmlInclude(typeof(Report01)]
[XmlInclude(typeof(Report02)]
public class BaseClass { }
public class Report01 : BaseClass { ... }
public class Report02 : BaseClass { ... }
List<BaseClass> objs = new List<BaseClass>();
List<BaseClass> objs2 = new List<BaseClass>();
// fill collections here
ObjectToXml(objs, "c:\\12\\objs.xml");
ObjectToXml(objs2, "c:\\12\\objs2.xml");
I know this is a popular topic and I have researched extensively without finding an answer to my problem.
I have a base class IntroductionAction and 2 derived classes IntroductionActionComplex and IntroductionActionSimple. I have a list of IntroductionAction objects to which I have added objects of both of the derived types. My classes are as follows:
[XmlInclude(typeof(IntroductionActionComplex))]
[XmlInclude(typeof(IntroductionActionSimple))]
public class IntroductionAction
{
public IntroductionAction() { }
}
public class IntroductionActionComplex : IntroductionAction
{
[XmlIgnore]
public string name { get; set; }
[XmlElement(ElementName = "QuestionString")]
public string question { get; set; }
[XmlElement(ElementName = "AnswerString")]
public List<string> answerStrings { get; set; }
public IntroductionActionComplex()
{
name = string.Empty;
question = null;
answerStrings = new List<string>();
}
}
public class IntroductionActionSimple : IntroductionAction
{
[XmlIgnore]
public string name { get; set; }
[XmlText]
public string Value { get; set; }
public IntroductionActionSimple()
{
Value = string.Empty;
}
}
I then create the List as follows
[XmlElement("IntroductionAction")]
public List<IntroductionAction> introductionActions { get; set; }
I am using XmlSerializer and everything serializes correctly. This is the resulting XML of the list containing one of each of the derived classes which is correct.
<IntroductionAction>
<QuestionString>
test
</QuestionString>
<AnswerString>
test
</AnswerString>
<AnswerString>
test
</AnswerString>
</IntroductionAction>
<IntroductionAction>
test
</IntroductionAction>
This XML file is going onto a device which doesn't read it as XML but just searches for the tags and does whatever work it needs to do and because of that the file can't contain any XSI or XSD tags, indentation, etc that is usually associated with proper XML.
My deserialization code is straight forward:
public static T Deserialize_xml_Config<T>(string file1, T obj)
{
XmlSerializer deserializer = new XmlSerializer(obj.GetType());
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
}
Finally to my problem. When I deserialize, it is being deserialized to the base class IntroductionAction and not to the derived classes.
These IntroductionAction classes are just part of a much larger object that I am serializing/deserializing. I have tried making the base class abstract since it contains no functionality but I get an error on deserialization saying
The specified type is abstract: name='IntroductionAction'
Despite my XmlIncludes it seems unable to find the derived classes.
I have tried adding the types to the serializer but that didn't work.
Any help is much appreciated.
Edit:
This is what I mean by adding the types to the serializer
XmlSerializer deserializer = new XmlSerializer(obj.GetType(), new Type [] { typeof(IntroductionActionComplex), typeof(IntroductionActionSimple) });
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
Also my attempt at using XmlAttributeOverrides:
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
var attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "IntroductionAction";
attr.Type = typeof(IntroductionActionComplex);
attrs.XmlElements.Add(attr);
attr.ElementName = "IntroductionAction";
attr.Type = typeof(IntroductionActionSimple);
attrs.XmlElements.Add(attr);
attrOverrides.Add(typeof(IntroductionAction), "IntroductionAction", attrs);
XmlSerializer deserializer = new XmlSerializer(obj.GetType(), attrOverrides);
using (TextReader reader = new StreamReader(file1))
{
return (T)deserializer.Deserialize(reader);
}
I think you are pretty close. Below is the full example of saving and loading the XML file based on derived class types. This will save the nodes as the derived type itself, so loading back in will keep the desired type, rather than convert back to the base type. You'll probably need to add exception handling, this was just a quick solution. I did not change your base IntroductionAction or the derived IntroductionActionComplex / IntroductionActionSimple classes.
public class RootNode
{
[XmlElement("IntroductionAction")]
public List<IntroductionAction> introductionActions { get; set; }
public RootNode()
{
introductionActions = new List<IntroductionAction>();
}
private static XmlAttributeOverrides GetXmlAttributeOverrides()
{
XmlAttributeOverrides xml_attr_overrides = new XmlAttributeOverrides();
XmlAttributes xml_attrs = new XmlAttributes();
xml_attrs.XmlElements.Add(new XmlElementAttribute(typeof(IntroductionActionComplex)));
xml_attrs.XmlElements.Add(new XmlElementAttribute(typeof(IntroductionActionSimple)));
xml_attr_overrides.Add(typeof(RootNode), "introductionActions", xml_attrs);
return xml_attr_overrides;
}
// Add exception handling
public static void SaveToFile(RootNode rootNode, string fileName)
{
using (MemoryStream mem_stream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(rootNode.GetType(), RootNode.GetXmlAttributeOverrides());
serializer.Serialize(mem_stream, rootNode);
using (BinaryWriter output = new BinaryWriter(new FileStream(fileName, FileMode.Create)))
{
output.Write(mem_stream.ToArray());
}
}
}
// Add exception handling
public static RootNode LoadFromFile(string fileName)
{
if (File.Exists(fileName))
{
using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (TextReader reader = new StreamReader(file))
{
XmlSerializer serializer = new XmlSerializer(typeof(RootNode), RootNode.GetXmlAttributeOverrides());
return (RootNode)serializer.Deserialize(reader);
}
}
}
return null;
}
}
Test program:
internal class Program
{
private static void Main(string[] args)
{
RootNode obj = new RootNode();
obj.introductionActions.Add(new IntroductionActionComplex() { question = "qTest", answerStrings = { "aTest1", "aTest2" }, name = "aName1" });
obj.introductionActions.Add(new IntroductionActionSimple() { name = "aName2", Value = "aValue" });
RootNode.SaveToFile(obj, "Test.xml");
RootNode obj2 = RootNode.LoadFromFile("Test.xml");
}
}
I am carrying out Xml serrialization using XmlSerializer. I am carrying out serialization of ClassA, which contains property named MyProperty of type ClassB. I don't want a particular property of ClassB to be serialized.
I have to use XmlAttributeOverrides as the classes are in another library.
If the property was in ClassA itself, it would have been straightforward.
XmlAttributeOverrides xmlOver = new XmlAttributeOverrides();
XmlAttributes xmlAttr = new XmlAttributes();
xmlAttr.XmlIgnore = true;
xmlOver.Add(typeof(ClassA), "MyProperty", xmlAttr);
XmlSerializer ser = new XmlSerializer(typeof(ClassA), xmlOver);
How to accomplish if the property is in ClassB and we need to serialize ClassA ?
You almost got it, just update your overrides to point to ClassB instead of ClassA:
XmlAttributeOverrides xmlOver = new XmlAttributeOverrides();
XmlAttributes xmlAttr = new XmlAttributes();
xmlAttr.XmlIgnore = true;
//change this to point to ClassB's property to ignore
xmlOver.Add(typeof(ClassB), "ThePropertyNameToIgnore", xmlAttr);
XmlSerializer ser = new XmlSerializer(typeof(ClassA), xmlOver);
Quick test, given:
public class ClassA
{
public ClassB MyProperty { get; set; }
}
public class ClassB
{
public string ThePropertyNameToIgnore { get; set; }
public string Prop2 { get; set; }
}
And exporting method:
public static string ToXml(object obj)
{
XmlAttributeOverrides xmlOver = new XmlAttributeOverrides();
XmlAttributes xmlAttr = new XmlAttributes();
xmlAttr.XmlIgnore = true;
xmlOver.Add(typeof(ClassB), "ThePropertyNameToIgnore", xmlAttr);
XmlSerializer xs = new XmlSerializer(typeof(ClassA), xmlOver);
using (MemoryStream stream = new MemoryStream())
{
xs.Serialize(stream, obj);
return System.Text.Encoding.UTF8.GetString(stream.ToArray());
}
}
Main method:
void Main()
{
var classA = new ClassA {
MyProperty = new ClassB {
ThePropertyNameToIgnore = "Hello",
Prop2 = "World!"
}
};
Console.WriteLine(ToXml(classA));
}
Outputs this with "ThePropertyNameToIgnore" omitted:
<?xml version="1.0"?>
<ClassA xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyProperty>
<Prop2>World!</Prop2>
</MyProperty>
</ClassA>