Have you too disliked the fact of having an object that, if you wanted to serialize it, required to have all variables to be exposed via a gettable and settable public property? Well, I have!
If I wanted to make an object with for example a read-only unique Id I had to expose it with a setter to be able to serialize it back and forth. This is really stupid since you don't want objects that are saved to database to have a changable ID. Therefore it would be very usefull to serialize private variables instead of public properties. This way you can serialize a class without having to expose all content to the developer using the class.
For this reason I created a class that uses the undocumented IXmlSerializable interface. Microsoft claims this interface is for internal use only but it does this exact trick like a charm.
It has 3 important Methods that you have to implement:
- WriteXml(System.Xml.XmlWriter)
- GetSchema()
- ReadXml(System.Xml.XmlReader)
These methods do exactly what they say. You implement the WriteXml method to create your xml, and use the ReadXml method to read it back into a new object.
the GetSchema() method is in case you want to validate your generated xml with a schema. I didn't use it because I used a flexible setup based on reflection.
/// <summary>
/// Converts the serializable members of an object into an XML document.
/// </summary>
/// <param name="writer">The XmlWriter used to write the XML-document instance.</param>
public void WriteXml(System.Xml.XmlWriter writer) {
foreach(FieldInfo fi in GetFields()){
string nodeName = fi.Name.Trim('_');
writer.WriteStartElement(nodeName);
if(fi.FieldType.BaseType == typeof(System.ValueType)){
writer.WriteString(fi.GetValue(this).ToString());
}else{
System.Text.StringBuilder result = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter( result );
try {
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer( fi.FieldType );
x.Serialize( new PrivateFieldXmlTextWriter(sw), fi.GetValue(this) );
}catch(Exception){
throw;
}
writer.WriteRaw(result.ToString());
}
writer.WriteEndElement(); // nodeName
}
}
here I iterate trough every private variable (in the FieldInfo class) and write a node with the value to the XmlWriter.
Sidenotes:
-
(The GetFields() method is used to get the FieldInfo array from a static hashtable if it was requested before for performance reasons or from the class itself by reflection)
-
You can see that I do something else for a field of a valuetype compared to a field of a referencetype. A valuetype can be written in the node itself. A reference type, a normal class, has to be serialized too. So here you see the serialisation code for serializing classes. But there is one thing different with normal serialisation, that is the use of the PrivateFieldXmlTextWriter. Instead of using a normal TextWriter I use my own that overrides the method to write out the xml version code (<?xml version="1.0"?>). This is because this should only be written out on the beginning of the xml document.
I do exactly the same the other way around for reading back into a class. the class must have a public default constructor to be able to instantiate a new class and then fill it afterwards. In .NET 2.0 this constructor may also be internal (if I'm right).
/// <summary>
/// Converts an XML document into an object using the specified reader.
/// </summary>
/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> used to read the XML document.</param>
public void ReadXml(System.Xml.XmlReader reader) {
XmlDocument doc = new XmlDocument();
doc.LoadXml(reader.ReadOuterXml());
foreach(FieldInfo fi in GetFields()){
string nodePath = "/" + this.GetType().Name + "/" + fi.Name.Trim('_');
XmlNode node = doc.SelectSingleNode(nodePath) ;
string val = node.InnerText;
fi.SetValue(this,ConvertXmlToDatatype(fi.FieldType,val));
}
}
What I do here is using XPath queries to get the content from the node and I made a method to return the object as the correct datatype. After that it is inserted via reflection into the variable it came from.
One thing you see al the time is the Trimming of the underscore of a variable. I do this so that you can make all your variables start with an underscore but still having valid xml without an underscore as the first character in the XmlNode. This wil make a variable like _id to be converted in Xml as <id>1</id>.
I hope you find this class very usefull and that it will solve many of your Xml Serialization problems.
Good luck.
PrivateFieldSerializer.zip (2.21 KB)