Is it possible via an attribute of some sort to serialize a string as CDATA using the .Net XmlSerializer?
-
2One thing worth noting about the two answers is that you don't need `CDataContent` if you're only reading XML. `XmlSerializer.Deserialize` will automatically turn it into text for you. – Chris S Nov 26 '11 at 12:31
9 Answers
[Serializable]
public class MyClass
{
public MyClass() { }
[XmlIgnore]
public string MyString { get; set; }
[XmlElement("MyString")]
public System.Xml.XmlCDataSection MyStringCDATA
{
get
{
return new System.Xml.XmlDocument().CreateCDataSection(MyString);
}
set
{
MyString = value.Value;
}
}
}
Usage:
MyClass mc = new MyClass();
mc.MyString = "<test>Hello World</test>";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
StringWriter writer = new StringWriter();
serializer.Serialize(writer, mc);
Console.WriteLine(writer.ToString());
Output:
<?xml version="1.0" encoding="utf-16"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyString><![CDATA[<test>Hello World</test>]]></MyString>
</MyClass>

- 4,254
- 1
- 36
- 27
-
4//In case you need empty CDATA you could set default if source value is null to avoid exception. `XmlDocument().CreateCDataSection(MyString ?? String.Empty);` – Asereware Nov 11 '14 at 21:41
-
@pr0gg3r does this also allow deserializing to the same object? I'm having trouble with that – Martin Apr 02 '15 at 20:47
-
How to create CDATA as a text value (and not as an element) such as
<![CDATA[ ? – mko May 17 '15 at 09:27Hello World ]]> -
1Only needs to be able to handle empty/null values than outputting
<![CDATA[]]> – bluee Oct 08 '15 at 05:42 -
Seems like a dirty nasty hack. Why does MSFT not provide a CDATA attribute you can just put on a string, or allow XmlElement attribute on strings... – jjxtra May 03 '19 at 20:23
-
In addition to the way posted by John Saunders, you can use an XmlCDataSection as the type directly, although it boils down to nearly the same thing:
private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{
get
{
XmlDocument doc = new XmlDocument();
return doc.CreateCDataSection( _message);
}
set
{
_message = value.Value;
}
}

- 11,391
- 14
- 81
- 114

- 32,368
- 11
- 87
- 99
-
1@Philip, does this work for deserialization? I've been seeing notes saying that the setter will receive an XmlText value. – John Saunders Sep 04 '09 at 19:55
-
1@John Saunders - It actually receives a XmlCharacterData value in the setter during deserialization, which is what the call to .Value is for in the setter ( I originally had it as ToString() from memory, but that was incorrect. ) – Philip Rieck Sep 08 '09 at 14:41
-
1@PhilipRieck What about if we need to wrap a custom object around a CDataSection. Create CDataSection accepts string. – zeppelin Dec 07 '15 at 07:37
-
[XmlRoot("root")]
public class Sample1Xml
{
internal Sample1Xml()
{
}
[XmlElement("node")]
public NodeType Node { get; set; }
#region Nested type: NodeType
public class NodeType
{
[XmlAttribute("attr1")]
public string Attr1 { get; set; }
[XmlAttribute("attr2")]
public string Attr2 { get; set; }
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] CDataContent
{
get
{
var dummy = new XmlDocument();
return new XmlNode[] {dummy.CreateCDataSection(Content)};
}
set
{
if (value == null)
{
Content = null;
return;
}
if (value.Length != 1)
{
throw new InvalidOperationException(
String.Format(
"Invalid array length {0}", value.Length));
}
Content = value[0].Value;
}
}
}
#endregion
}

- 160,644
- 26
- 247
- 397
-
9To me this doesn't seem like the most elegant solution. Is this the only possible way of doing this? – jamesaharvey Sep 04 '09 at 15:36
-
1I think this is the only way to accomplish this, I've seen this topic elsewhere and always the same answer. The example from Philip is a little cleaner but the same concept. The only other way I know of is to implement your own IXmlSerializable on a class that represents the CDATA content. – csharptest.net Sep 04 '09 at 16:57
-
I wanted to do the same thing because it seems like storing strings as CDATA appears to imply less processing time, as with it we could 'only' read/write string 'as is'. How expensive is involving XmlDocument/XmlCDataSection instances? – tishma Apr 09 '12 at 15:04
-
And the whole Attributes thing is there so we are able to keep the domain model classes clean from serialization logic details. It's sooo sad if the dirty way is the only way. – tishma Apr 09 '12 at 15:06
-
Were you aware that the XML Serializer is pretty much legacy technology? It's not going to have any improvements made to it, only the most critical bug fixes. – John Saunders Apr 09 '12 at 18:02
-
2Philip's solution slightly farther down the page is a tidier thing to do. – Karl May 17 '12 at 15:27
-
Though this may work for strings, I think it may not work for true binary data (like images or so). Isn't the UTF encoding / decoding done in a stream-reader that is wrapped around the whole serializer? In that case you'll have to use a FileStream or an other type of non-encoding stream, and encode/decode the non-cdata parts seperately. Even implementing the [IXmlSerializable](https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable) Interface for a specific CDATA type would not solve this problem. – Louis Somers Jan 04 '19 at 08:40
In the class to be serialized:
public CData Content { get; set; }
And the CData class:
public class CData : IXmlSerializable
{
private string _value;
/// <summary>
/// Allow direct assignment from string:
/// CData cdata = "abc";
/// </summary>
/// <param name="value">The string being cast to CData.</param>
/// <returns>A CData object</returns>
public static implicit operator CData(string value)
{
return new CData(value);
}
/// <summary>
/// Allow direct assignment to string:
/// string str = cdata;
/// </summary>
/// <param name="cdata">The CData being cast to a string</param>
/// <returns>A string representation of the CData object</returns>
public static implicit operator string(CData cdata)
{
return cdata._value;
}
public CData() : this(string.Empty)
{
}
public CData(string value)
{
_value = value;
}
public override string ToString()
{
return _value;
}
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
_value = reader.ReadElementString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteCData(_value);
}
}
-
-
3This answer deserves more recognition. Although, the customised CData type no longer has those convenient built-in methods that the System.String type enjoys. – Lionet Chen Jan 20 '17 at 00:01
-
-
Answer works great. It is a shame that XmlElement does not work on string field, then you could just add a cdata type, but whatever... – jjxtra May 03 '19 at 20:28
-
I had a similar need but required a different output format - I wanted an attribute on the node that contains the CDATA. I took some inspiration from the above solutions to create my own. Maybe it will help someone in the future...
public class EmbedScript
{
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public XmlNode[] Script { get; set; }
public EmbedScript(string type, string script)
{
Type = type;
Script = new XmlNode[] { new XmlDocument().CreateCDataSection(script) };
}
public EmbedScript()
{
}
}
In the parent object to be serialised, I have the following property:
[XmlArray("embedScripts")]
[XmlArrayItem("embedScript")]
public List<EmbedScript> EmbedScripts { get; set; }
I get the following output:
<embedScripts>
<embedScript type="Desktop Iframe">
<![CDATA[<div id="play_game"><iframe height="100%" src="http://www.myurl.com" width="100%"></iframe></div>]]>
</embedScript>
<embedScript type="JavaScript">
<![CDATA[]]>
</embedScript>
</embedScripts>

- 1,512
- 1
- 20
- 24
In my case I'm using mixed fields, some CDATA some not, at least for me the following solution is working....
By always reading the Value field, I'm getting the contents, regardless whether CDATA or just plain text.
[XmlElement("")]
public XmlCDataSection CDataValue {
get {
return new XmlDocument().CreateCDataSection(this.Value);
}
set {
this.Value = value.Value;
}
}
[XmlText]
public string Value;
Better late than never.
Cheers

- 51
- 2
-
Fantastic - I have a feeling this answer saved me a chunk of time! For info I used the [XmlIgnore] attribute on Value – d219 Jul 29 '20 at 10:46
-
How is this operationally different than [pr0gg3r's answer](https://stackoverflow.com/a/13801704/1028230)? – ruffin Aug 04 '20 at 21:40
This implementation has the ability to process nested CDATA within the string you're encoding (based on John Saunders original answer).
For example, suppose you wanted to encode the following literal string into CDATA:
I am purposefully putting some <![CDATA[ cdata markers right ]]> in here!!
You would want the resultant output to look something like this:
<![CDATA[I am purposefully putting some <![CDATA[ cdata markers right ]]]]><![CDATA[> in here!!]]>
The following implementation will loop over the string, split up instances of ...]]>...
into ...]]
and >...
and create separate CDATA sections for each.
[XmlRoot("root")]
public class Sample1Xml
{
internal Sample1Xml()
{
}
[XmlElement("node")]
public NodeType Node { get; set; }
#region Nested type: NodeType
public class NodeType
{
[XmlAttribute("attr1")]
public string Attr1 { get; set; }
[XmlAttribute("attr2")]
public string Attr2 { get; set; }
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] CDataContent
{
get
{
XmlDocument dummy = new XmlDocument();
List<XmlNode> xmlNodes = new List<XmlNode>();
int tokenCount = 0;
int prevSplit = 0;
for (int i = 0; i < Content.Length; i++)
{
char c = Content[i];
//If the current character is > and it was preceded by ]] (i.e. the last 3 characters were ]]>)
if (c == '>' && tokenCount >= 2)
{
//Put everything up to this point in a new CData Section
string thisSection = Content.Substring(prevSplit, i - prevSplit);
xmlNodes.Add(dummy.CreateCDataSection(thisSection));
prevSplit = i;
}
if (c == ']')
{
tokenCount++;
}
else
{
tokenCount = 0;
}
}
//Put the final part of the string into a CData section
string finalSection = Content.Substring(prevSplit, Content.Length - prevSplit);
xmlNodes.Add(dummy.CreateCDataSection(finalSection));
return xmlNodes.ToArray();
}
set
{
if (value == null)
{
Content = null;
return;
}
if (value.Length != 1)
{
throw new InvalidOperationException(
String.Format(
"Invalid array length {0}", value.Length));
}
Content = value[0].Value;
}
}
}

- 6,578
- 8
- 43
- 68
This works pretty well
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
public class CDataContent
{
public CDataContent()
{
}
public CDataContent(string content)
{
this.Content = content;
}
[XmlIgnore]
public string Content
{
get => this.CData.FirstOrDefault()?.Value;
set
{
this.CData.Clear();
this.CData.Add(new XmlDocument().CreateCDataSection(value));
}
}
[XmlText]
public Collection<XmlNode> CData { get; } = new();
public static implicit operator CDataContent(string value) => new(value);
public static implicit operator string(CDataContent value) => value.Content;
}

- 9,407
- 5
- 63
- 81
I've done a small modification to pr0gg3r's code. This one works with empty/null values: It returns an empty <![CDATA[]]>
if an empty string is assigned ex: mc.MyString = "";
and it returns nothing if nothing was assigned. This solution is useful in cases when the xml element is required in some positions and not-required in other positions.
[Serializable]
public class MyClass
{
public MyClass() { }
[XmlIgnore]
public string MyString { get; set; }
[XmlElement("MyString")]
public System.Xml.XmlCDataSection MyStringCDATA
{
get
{
if (MyString == null)
{
return null;
}
else
{
return new System.Xml.XmlDocument().CreateCDataSection(MyString);
}
}
set
{
MyString = value.Value;
}
}
}

- 1
- 1