Wrap properties with CData Section - XML Serialization C#
With some effort and customization, it possible to get close to what you want, however XmlSerializer
will always place the CData
nodes at the end of the container element. Your example XML shows the CData
nodes between specific nodes of the container element. As long as you don't need this precise control, you can use How do you serialize a string as CDATA using XmlSerializer? to do nested serializations, like so:
public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }
[DataMember]
[JsonProperty]
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public List<Option> ListB { get; set; }
[DataMember]
public List<string> ListC { get; set; }
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public Product Product { get; set; }
[XmlText] // NECESSARY TO EMIT CDATA NODES
[IgnoreDataMember]
[JsonIgnore]
public XmlNode[] CDataContent
{
get
{
return XmlWrapCDataHelper.GetCDataContent(this);
}
set
{
XmlWrapCDataHelper.SetCDataContent(this, value);
}
}
}
public class Product
{
public string ProductId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
public class Option
{
public string OptionValue { get; set; }
public string OptionName { get; set; }
}
Using the following extension methods and custom attribute:
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class XmlWrapCDataAttribute : Attribute
{
public XmlWrapCDataAttribute() { this.Namespace = string.Empty; }
public XmlWrapCDataAttribute(string name) : this() { this.Name = name; }
public string Name { get; set; }
public string Namespace { get; set; }
}
public static class XmlWrapCDataHelper
{
static Tuple<PropertyInfo, XmlWrapCDataAttribute> [] XmlWrapCDataProperties(Type type)
{
return type.GetProperties()
.Where(p => p.GetGetMethod() != null && p.GetSetMethod() != null)
.Select(p => Tuple.Create(p, p.GetCustomAttribute<XmlWrapCDataAttribute>()))
.Where(p => p.Item2 != null)
.ToArray();
}
public static XmlNode[] GetCDataContent(object obj)
{
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType());
return properties.Select(p => (XmlNode)p.Item1.GetValue(obj, index).GetCData(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace)).ToArray();
}
public static void SetCDataContent(object obj, XmlNode [] nodes)
{
if (nodes == null || nodes.Length < 1)
return;
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType()).ToDictionary(p => XName.Get(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace), p => p);
var xml = "<Root>" + String.Concat(nodes.Select(c => c.Value)) + "</Root>";
foreach (var element in XElement.Parse(xml).Elements())
{
Tuple<PropertyInfo, XmlWrapCDataAttribute> pair;
if (properties.TryGetValue(element.Name, out pair))
{
var value = element.Deserialize(pair.Item1.PropertyType, element.Name.LocalName, element.Name.Namespace.NamespaceName);
pair.Item1.SetValue(obj, value, index);
}
}
}
}
public static class XmlSerializationHelper
{
public static XmlCDataSection GetCData(this object obj, string rootName, string rootNamespace)
{
return obj == null ? null : new System.Xml.XmlDocument().CreateCDataSection(obj.GetXml(XmlSerializerFactory.Create(obj.GetType(), rootName, rootNamespace)));
}
public static XCData GetCData(this object obj, XmlSerializer serializer = null)
{
return obj == null ? null : new XCData(obj.GetXml(serializer));
}
public static string GetXml(this object obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
public static object Deserialize(this XContainer element, Type type, string rootName = null, string rootNamespace = null)
{
return element.Deserialize(type, XmlSerializerFactory.Create(type, rootName, rootNamespace));
}
public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer = null)
{
using (var reader = element.CreateReader())
{
return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
}
}
public static T DeserializeXML<T>(this string xmlString, XmlSerializer serializer = null)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
}
}
public static class XmlSerializerFactory
{
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
This will parse your provided XML successfully, and in return generate XML that looks like:
<Order>
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T05:10:49.6031106-05:00</Date>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC><![CDATA[<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>]]><![CDATA[<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>]]></Order>
XmlSerialize Class to CDATA
One of the ways to get the output you are expecting is to separate
the creation of the module
data and generating the CDATA
part. For example:
To create the module
data -
Create a class to hold module details as below -
public class Module
{
public string title { get; set; }
public string code { get; set; }
public string level { get; set; }
public string summary { get; set; }
}Create a method to fetch these datails -
public static string CreateXMLString()
{
List<Module> modules = new List<Module>();
modules = new List<Module>() { new Module() { code = "test",
summary="Test3", title="Test", level = "tests1" },
new Module() { code = "test3",
summary="Test3", title="Test3", level = "tests3" } };
// Create XML to return the string in the format of
// <module code="test">
// < level > tests1 </ level >
// < summary > Test3 </ summary >
// < title > Test </ title >
//</ module >< module code = "test3" >
// < level > tests3 </ level >
// < summary > Test3 </ summary >
// < title > Test3 </ title >
// </ module >
var modulesXml =
from mod in modules
select new XElement("module",
new XAttribute("code", mod.code),
new XElement("level", mod.level),
new XElement("summary", mod.summary),
new XElement("title", mod.title)
);
return String.Concat(modulesXml);
}
To get the CDATA
you can use the below steps -
Create a class
Modules
and refer the documentation for usages ofCreateCDataSection
and forsimilar threads here
for the details[XmlType("")]
public class Modules
{
public Modules() { }
[XmlIgnore]
public string Message { get; set; }
[XmlElement("modules")]
public System.Xml.XmlCDataSection MyStringCDATA
{
get
{
return new System.Xml.XmlDocument().CreateCDataSection(Message);
}
set
{
Message = value.Value;
}
}
}
To test the output assign the string generated in step 2 during serialization you can refer the sample code below
static void Main(string[] args)
{
Modules mc = new Modules();
mc.Message = CreateXMLString();//Assign your data created in step 2
XmlSerializer serializer = new XmlSerializer(typeof(Modules));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
StringWriter writer = new StringWriter();
//Remove unnecessary namespaces
serializer.Serialize(writer, mc,ns);
var test = XDocument.Parse(writer.ToString());
var data = test.Root.Elements();
Console.WriteLine(data.FirstOrDefault().Value);
}
Output -
<modules>
<![CDATA[<module>
<code>test</code>
<level>tests1</level>
<summary>Test3</summary>
<title>Test</title>
</module><module>
<code>test3</code>
<level>tests3</level>
<summary>Test3</summary>
<title>Test3</title>
</module>]]>
</modules>
How do you serialize a string as CDATA using XmlSerializer?
[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
}
Deserialize XML CData with attribute
You have a good start there.. this should help you get the rest of the way.
public class Level
{
[XmlAttribute]
public string ID {get; set;}
public Selection Selection {get; set;}
}
public class Selection {
[XmlAttribute]
public string Name {get;set;}
public Content Content {get;set;}
}
public class Content {
[XmlText]
public string Data {get;set;}
}
So to access that CDATA text via your object model you would access Level.Selection.Content.Data
.
Deserialize XML with XmlSerializer where XmlElement names differ but have same content
You can adopt the approach from this answer and add a surrogate XmlElement []
property, marked with [XmlAnyElement]
, that performs a nested (de)serialization on the key/value pairs of the Dictionary<string, GroupName>
property, binding the dictionary keys to the element names.
Note that, while the documentation for XmlAnyElementAttribute
states
Specifies that the member (a field that returns an array of XmlElement or XmlNode objects) contains objects that represent any XML element that has no corresponding member in the object being serialized or deserialized.
In fact the attribute can be applied to a property as well. Thus a (de)serialization callback is not required since the nested serialization can be performed inside the getter and setter for the surrogate property itself. It can also be applied to members returning an array of XElement
objects instead of XmlElement
if you prefer the new LINQ-to-XML API.
In this approach, your DeactivationsGroup
would look like:
[Serializable()]
public class DeactivationsGroup
{
public DeactivationsGroup() { this.GroupNames = new Dictionary<string, GroupName>(); }
[XmlIgnore]
public Dictionary<string, GroupName> GroupNames { get; set; }
public int Level { get; set; }
[XmlAttribute]
public byte index { get; set; }
[XmlAnyElement]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XElement[] XmlGroupNames
{
get
{
return GroupNames.SerializeToXElements(null);
}
set
{
if (value == null || value.Length < 1)
return;
foreach (var pair in value.DeserializeFromXElements<GroupName>())
{
GroupNames.Add(pair.Key, pair.Value);
}
}
}
}
Making use of the following extension methods and classes:
public static class XmlKeyValueListHelper
{
const string RootLocalName = "Root";
public static XElement [] SerializeToXElements<T>(this IEnumerable<KeyValuePair<string, T>> dictionary, XNamespace ns)
{
if (dictionary == null)
return null;
ns = ns ?? "";
var serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
var array = dictionary
.Select(p => new { p.Key, Value = p.Value.SerializeToXElement(serializer, true) })
// Fix name and remove redundant xmlns= attributes. XmlWriter will add them back if needed.
.Select(p => new XElement(ns + p.Key, p.Value.Attributes().Where(a => !a.IsNamespaceDeclaration), p.Value.Elements()))
.ToArray();
return array;
}
public static IEnumerable<KeyValuePair<string, T>> DeserializeFromXElements<T>(this IEnumerable<XElement> elements)
{
if (elements == null)
yield break;
XmlSerializer serializer = null;
XNamespace ns = null;
foreach (var element in elements)
{
if (serializer == null || element.Name.Namespace != ns)
{
ns = element.Name.Namespace;
serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
}
var elementToDeserialize = new XElement(ns + RootLocalName, element.Attributes(), element.Elements());
yield return new KeyValuePair<string, T>(element.Name.LocalName, elementToDeserialize.Deserialize<T>(serializer));
}
}
public static XmlSerializerNamespaces NoStandardXmlNamespaces()
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
return ns;
}
public static XElement SerializeToXElement<T>(this T obj)
{
return obj.SerializeToXElement(null, NoStandardXmlNamespaces());
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns)
{
return obj.SerializeToXElement(null, ns);
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
return obj.SerializeToXElement(serializer, (omitStandardNamespaces ? NoStandardXmlNamespaces() : null));
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
return (T)result;
}
}
}
public static class XmlSerializerFactory
{
// To avoid a memory leak the serializer must be cached.
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
// This factory taken from
// https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
Sample fiddle. And another demonstrating a case with XML namespaces and attributes.
How to serialize an array to XML with dynamic tag names
Using XDocument :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication45
{
class Program
{
static void Main(string[] args)
{
string xmlIdent = "<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
"<GeneralInformation xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"</GeneralInformation>";
XDocument doc = XDocument.Parse(xmlIdent);
XElement generalInfo = doc.Root;
XElement infoList = new XElement("InfoList");
generalInfo.Add(infoList);
for (int i = 0; i < 10; i++)
{
infoList.Add(new XElement("Infor" + i.ToString("0##"), new XElement("InfoName", "Test" + i.ToString("0##"))));
}
}
}
}
//<?xml version="1.0" encoding="utf-16"?>
//<GeneralInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
// <InfoList>
// <Info001>
// <InfoName>Test1</InfoName>
// </Info001>
// <Info002>
// <InfoName>Test2</InfoName>
// </Info002>
// <Info003>
// <InfoName>Test3</InfoName>
// </Info003>
// </InfoList>
//</GeneralInformation>
Related Topics
How to Optionally Turn Off the JSONignore Attribute at Runtime
Is the C# Compiler Smart Enough to Optimize This Code
How to Send a File and Form Data with Httpclient in C#
Redirecting Output to the Text File C#
Generics: Casting and Value Types, Why Is This Illegal
Discovering Derived Types Using Reflection
Crud Operations Using Datagridview, Datatable and Dataadapter - Cannot Add New Row to Datagridview
How Can Xml Documentation for Web API Include Documentation from Beyond the Main Project
How to Use a Client Certificate to Authenticate and Authorize in a Web API
Sending and Receiving Data Over a Network Using Tcpclient
Put Content in Httpresponsemessage Object
Dependency Injection VS Service Location
Cannot Find .Cs Files for Debugging .Net Source Code
Start May Not Be Called on a Promise-Style Task. Exception Is Coming