Whilst simple proxies work with single values, you have to do a deeper proxying for collections, because of the way the .NET XML serialization machinery works:
[XmlRootAttribute("root")]
public class TestConfig
{
public TestConfig()
{
TestList = new List<string>();
}
private List<string> testList;
[XmlIgnore]
public List<string> TestList
{
get
{
if (this.testList == null)
{
var newCollection = new List<string>();
if (this.cdataList != null)
{
foreach (var x in this.cdataList)
{
newCollection.Add(x.Value);
}
}
this.testList = newCollection;
this.cdataList = null;
}
return this.testList;
}
set
{
this.testList = value;
this.cdataList = null;
}
}
private List<XmlCDataSection> cdataList;
[XmlArray("tlist")]
[XmlArrayItem("item")]
public List<XmlCDataSection> CdataList
{
get
{
if (this.cdataList == null)
{
var newCollection = new List<XmlCDataSection>();
if (this.testList != null)
{
foreach (var x in this.testList)
{
newCollection.Add(new XmlDocument().CreateCDataSection(x));
}
}
this.cdataList = newCollection;
this.testList = null;
}
return this.cdataList;
}
set
{
this.cdataList = value;
this.testList = null;
}
}
public void Save(string path)
{
var serializer = new XmlSerializer(GetType());
using (var stream = new StreamWriter(path))
{
serializer.Serialize(stream, this);
}
}
public static TestConfig Load(string path)
{
var serializer = new XmlSerializer(typeof(TestConfig));
using (var stream = new StreamReader(path))
{
return (TestConfig)serializer.Deserialize(stream);
}
}
}
The problem is that the serialization code doesn't just get and set the collections in one go. For example, when it's deserializing, it either creates a new collection, or gets one that's already set on the property, and adds to it. If you've created a new collection here computed from the "real" collection that your application needs to deal with, then any changes to the computed collection won't be reflected in the "real" collection.
To work around this, what I've done in the code above is to transfer ownership of the collection from the "real" collection to the "proxy" collection, and back again, depending on which collection property is being accessed. The cost of transferring ownership is incurred only when switching from one property to the other, so successive accesses to the "real" TestList
collection in your application won't incur that expense.
This is somewhat inelegant though if you have many such collections. If you wanted to have all your element text serialized as CDATA, you could implement a custom XmlWriter
, like the following:
/// <summary>
/// Custom XmlWriter.
/// Wraps up another XmlWriter to intercept string writes within
/// elements and writes them as CDATA instead.
/// </summary>
public class XmlCDataWriter : XmlWriter
{
XmlWriter w;
public XmlCDataWriter(XmlWriter baseWriter)
{
this.w = baseWriter;
}
public override void Close()
{
w.Close();
}
public override void Flush()
{
w.Flush();
}
public override string LookupPrefix(string ns)
{
return w.LookupPrefix(ns);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
w.WriteBase64(buffer, index, count);
}
public override void WriteCData(string text)
{
w.WriteCData(text);
}
public override void WriteCharEntity(char ch)
{
w.WriteCharEntity(ch);
}
public override void WriteChars(char[] buffer, int index, int count)
{
w.WriteChars(buffer, index, count);
}
public override void WriteComment(string text)
{
w.WriteComment(text);
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
w.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteEndAttribute()
{
w.WriteEndAttribute();
}
public override void WriteEndDocument()
{
w.WriteEndDocument();
}
public override void WriteEndElement()
{
w.WriteEndElement();
}
public override void WriteEntityRef(string name)
{
w.WriteEntityRef(name);
}
public override void WriteFullEndElement()
{
w.WriteFullEndElement();
}
public override void WriteProcessingInstruction(string name, string text)
{
w.WriteProcessingInstruction(name, text);
}
public override void WriteRaw(string data)
{
w.WriteRaw(data);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
w.WriteRaw(buffer, index, count);
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
w.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteStartDocument(bool standalone)
{
w.WriteStartDocument(standalone);
}
public override void WriteStartDocument()
{
w.WriteStartDocument();
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
w.WriteStartElement(prefix, localName, ns);
}
public override WriteState WriteState
{
get { return w.WriteState; }
}
public override void WriteString(string text)
{
if (WriteState == WriteState.Element)
{
w.WriteCData(text);
}
else
{
w.WriteString(text);
}
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
w.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteWhitespace(string ws)
{
w.WriteWhitespace(ws);
}
}
You'd then use it like follows:
var serializer = new XmlSerializer(...));
using (var cdataWriter = new XmlCDataWriter(XmlWriter.Create("somepath.xml")))
{
serializer.Serialize(cdataWriter, myDocumentObject);
}
Again, this only makes sense as an option if you want to write everything as CDATA.