I have a problem with CDATA deserialization using standard .Net XmlSerializer.
Update: I get XML from external system and I can't influence it's format so I can't make CData be enclosed in a separate Element of Attribute.
Serialization gives this:
<?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"><![CDATA[Hello, world!]]></MyClass>
Deserialization does not restore object to it's original state.
Here's class that is being serialized:
public class MyClass
{
string _data;
[XmlIgnore]
public string Data
{
get { return _data; }
set { _data = value; }
}
[XmlAnyElement]
public XmlCDataSection CData
{
get { return new XmlDataDocument().CreateCDataSection(Data); }
set { Data = value.Value; }
}
}
Here's the test which fails:
[Test]
public void CData_as_inner_text_test()
{
MyClass item = new MyClass();
item.Data = "Hello, world!";
XmlSerializer serializer = new XmlSerializer(item.GetType());
string serialized;
using (StringWriter sw = new StringWriter())
{
serializer.Serialize(sw, item);
serialized = sw.GetStringBuilder().ToString();
}
MyClass deserialized;
using (StringReader sr = new StringReader(serialized))
{
deserialized = (MyClass)serializer.Deserialize(sr);
}
Assert.AreEqual(item.Data, deserialized.Data); // For some reason, deserialized.Data == null
}
I found the same problem here but there's no answer:
XmlSerializer, XmlAnyElement and CDATA
The CData property ends up null, because the content of the CDATA section ends up in the Data property, where it is being ignored...
<MyClass><![CDATA[Hello, world!]]></MyClass>
is absolutely equivalent to:
<MyClass>Hello, world!</MyClass>
You shouldn't care whether the external app writes the content of MyClass as CData or not. Likewise, the external app shouldn't care how you write it out.
IOW, this should be all you need:
public class MyClass
{
string _data;
[XmlText]
public string Data
{
get { return _data; }
set { _data = value; }
}
}
First declare a property as XmlCDataSection
public XmlCDataSection ProjectXml { get; set; }
in this case projectXml is a string xml
ProjectXml = new XmlDocument().CreateCDataSection(projectXml);
when you serialize your message you will have your nice format (notice )
<?xml version="1.0" encoding="utf-16"?>
<MessageBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="Message_ProjectStatusChanged">
<ID>131</ID>
<HandlerName>Plugin</HandlerName>
<NumRetries>0</NumRetries>
<TriggerXml><![CDATA[<?xml version="1.0" encoding="utf-8"?><TmData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="9.0.0" Date="2012-01-31T15:46:02.6003105" Format="1" AppVersion="10.2.0" Culture="en-US" UserID="0" UserRole=""><PROJECT></PROJECT></TmData>]]></TriggerXml>
<MessageCreatedDate>2012-01-31T20:28:52.4843092Z</MessageCreatedDate>
<MessageStatus>0</MessageStatus>
<ProjectId>0</ProjectId>
<UserGUID>8CDF581E44F54E8BAD60A4FAA8418070</UserGUID>
<ProjectGUID>5E82456F42DC46DEBA07F114F647E969</ProjectGUID>
<PriorStatus>0</PriorStatus>
<NewStatus>3</NewStatus>
<ActionDate>0001-01-01T00:00:00</ActionDate>
</MessageBase>
Related
I have the following c# object:
class Modification {
public string Name;
public string Value;
}
I want to use the serializer to serialize my object the following way:
<name>value</name>
Example: Let's say we set those variables to
Name = "Autoroute"
Value = 53
I want the xml to look like:
<test>
<Autoroute>53</Autoroute>
</test>
I saw somewhere that this feature is not supported by the serializer, but is there a way to overload the serializer to allow this kind of behavior ?
Changing the XML structure is not an option since it is already a convention.
You can use IXmlSerializable to do this, though this doesn't give you control over the root element name - you have to set this in the serializer (which may present other challenges when you come to read it as part of a larger xml structure...).
public class Modification : IXmlSerializable
{
public string Name;
public string Value;
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Name = reader.Name;
Value = reader.ReadElementContentAsString();
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString(Name, Value);
}
}
Usage,
Modification modification = new Modification()
{
Name = "Autoroute",
Value = "53"
};
Modification andBack = null;
string rootElement = "test";
XmlSerializer s = new XmlSerializer(typeof(Modification), new XmlRootAttribute(rootElement));
using (StreamWriter writer = new StreamWriter(#"c:\temp\output.xml"))
s.Serialize(writer, modification);
using (StreamReader reader = new StreamReader(#"c:\temp\output.xml"))
andBack = s.Deserialize(reader) as Modification;
Console.WriteLine("{0}={1}", andBack.Name, andBack.Value);
The XML produced by this looks like this,
<?xml version="1.0" encoding="utf-8"?>
<test>
<Autoroute>53</Autoroute>
</test>
I want to create a serialisable class to represent an xml envelope that can contain arbitrary message content. Example xml (simplified) below:
<?xml version="1.0" encoding="utf-8"?>
<Envelope>
<MessageA>
<Url></Url>
</MessageA>
</Envelope>
<?xml version="1.0" encoding="utf-8"?>
<Envelope>
<MessageB>
<Value></Value>
</MessageB>
</Envelope>
My idea is to use a generic envelope class to handle this:
[Serializable]
[XmlRoot(ElementName="Envelope")]
public class Envelope<TContent> where TContent : new()
{
public Envelope()
{
Content = new TContent();
}
public TContent Content { get; set; }
}
[Serializable]
public class MessageA
{
public string Url { get; set; }
}
[Serializable]
public class MessageB
{
public string Value { get; set; }
}
These could be serialised so:
var envelope = new Envelope<MessageA>();
envelope.Content.Url = "http://www.contoso.com";
string xml = envelope.ToXml();
However, instead of the xml message that I want (per examples above), I get the following. How can I change the classes or the serialisation process to rename the Content element to the name of the Message itself?
<?xml version="1.0" encoding="utf-8"?>
<Envelope>
<Content>
<Url>http://www.contoso.com</Url>
</Content>
</Envelope>
Found that the XmlAttributeOverrides class has a solution for this problem. Adding the following method to the Envelope class causes the ToXml() extension method to serialise the object as required.
public string ToXml()
{
return ToXml(typeof(TContent).Name);
}
public string ToXml(string contentElementName)
{
XmlElementAttribute element = new XmlElementAttribute(contentElementName, typeof(TContent));
XmlAttributes attributes = new XmlAttributes();
attributes.XmlElements.Add(element);
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Envelope<TContent>), "Content", attributes);
return ToXml(this, overrides);
}
public string ToXml(Envelope<TContent> value, XmlAttributeOverrides overrides)
{
using (var sw = new Utf8StringWriter())
{
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = omitXmlDeclaration,
Indent = true
};
XmlSerializer xs = new XmlSerializer(typeof(Envelope<TContent>), overrides);
using (XmlWriter writer = XmlWriter.Create(sw, settings))
{
xs.Serialize(writer, value);
}
return sw.ToString();
}
}
How can I deserialize the string based on what I have done in this method? Basically, what I have here is to pass the string through the network using serialization and deserialize the string in order to convey the message. But once I managed to receive the message, I have no idea if what I'm doing is correct. Here's the code:
string ConvertToString(FrogGame np, Frog1 pm, Frog2 pm2) //Serialization. the three parameters are the classes.
{
XmlSerializer sendSerializer = new XmlSerializer(typeof(FrogGame),new Type[]{typeof(Frog1),typeof(Frog2)});
StreamWriter myWriter = new StreamWriter(#"pad1.xml");
sendSerializer.Serialize(myWriter, np);
sendSerializer.Serialize(myWriter, pm);
sendSerializer.Serialize(myWriter, pm2);
return myWriter.ToString();
} //Overall, I serialize it into string
Once I pass the string through the network, I want to deserialize it in order the pass the message to the classes. How do I continue here onwards? How can I edit? The code:
void StringReceived(string str) //so str is myWriter.ToString()
{
XmlSerializer revSerializer = new XmlSerializer(typeof(FrogGame), new Type[] { typeof(Frog1), typeof(Frog2) });
FileStream myFileStream = new FileStream(#"pad1.xml", FileMode.Open);
FrogGame b = (FrogGame)revSerializer.Deserialize(myFileStream);
if (b is Frog1)
{
if (Network.IsServer())
{
pm = (Frog1)b;
pm.Position.Y = b.pm.Position.Y;
pm.Position.X = b.pm.Position.X;
}
else
{
System.Diagnostics.Debug.WriteLine("BAD Message: " + msg);
}
}
else if (b is Frog2)
{
if (Network.IsClient())
{
pm2 = (PaddleMessage2)b;
pm2.Position.Y = b.pm2.Position.Y;
pm2.Position.X = b.pm2.Position.X;
}
else
{
System.Diagnostics.Debug.WriteLine("BAD Message: " + msg);
}
}
}
I might misinterpret your problem, but I why don't you put all the thing you want to save in a class and do it like this (plus, if you use class, your data "transportation" and "management" will be much easier) :
SERIALIZATION
XmlSerializer serializer = new XmlSerializer(typeof(FrogGameData));
TextWriter textWriter = new StreamWriter("FrogGameSaveFile.xml");
serializer.Serialize(textWriter, _frogGameData);
textWriter.Close();
DESERIALIZATION
XmlSerializer deserializer = new XmlSerializer(typeof(FrogGameData));
TextReader textReader = new StreamReader("FrogGameSaveFile.xml");
_frogGameData = (FrogGameData)deserializer.Deserialize(textReader);
textReader.Close();
Note : The need-to-be-saved field should have property, because the tag in the XML will mimic the property name.
Additional Note : FrogGameData is not different than a normal class for automatic serialization like this. The XML will mimic your property order in the class for the one in the XML file.
But if you wanna need to rearrange the XML tag placement, you could do something like [XmlElement(Order = 1)],[XmlElement(Order = 2)], etc on top of your property to customize the order in XML file.
UPDATE
In case you need it, this is an example of your FrogGameData class :
public class FrogGameData
{
private Frog _frog1;
private Frog _frog2;
public Frog Frog1
{
get { return _frog1; }
set { _frog1 = value; }
}
public Frog Frog2
{
get { return _frog2; }
set { _frog2 = value; }
}
}
And the XML will pretty much like this :
<?xml version="1.0" encoding="utf-8"?>
<FrogGameData>
<Frog1>Something-depends-on-your-data</Frog1>
<Frog2>Something-depends-on-your-data</Frog2>
</FrogGameData>
But, if your class is (Note the XmlElement part) :
public class FrogGameData
{
private Frog _frog1;
private Frog _frog2;
[XmlElement(Order = 2)]
public Frog Frog1
{
get { return _frog1; }
set { _frog1 = value; }
}
[XmlElement(Order = 1)]
public Frog Frog2
{
get { return _frog2; }
set { _frog2 = value; }
}
}
Then, your XML will be :
<?xml version="1.0" encoding="utf-8"?>
<FrogGameData>
<Frog2>Something-depends-on-your-data</Frog2>
<Frog1>Something-depends-on-your-data</Frog1>
</FrogGameData>
When using a standard .NET Xml Serializer, is there any way I can hide all null values? The below is an example of the output of my class. I don't want to output the nullable integers if they are set to null.
Current Xml output:
<?xml version="1.0" encoding="utf-8"?>
<myClass>
<myNullableInt p2:nil="true" xmlns:p2="http://www.w3.org/2001/XMLSchema-instance" />
<myOtherInt>-1</myOtherInt>
</myClass>
What I want:
<?xml version="1.0" encoding="utf-8"?>
<myClass>
<myOtherInt>-1</myOtherInt>
</myClass>
You can create a function with the pattern ShouldSerialize{PropertyName} which tells the XmlSerializer if it should serialize the member or not.
For example, if your class property is called MyNullableInt you could have
public bool ShouldSerializeMyNullableInt()
{
return MyNullableInt.HasValue;
}
Here is a full sample
public class Person
{
public string Name {get;set;}
public int? Age {get;set;}
public bool ShouldSerializeAge()
{
return Age.HasValue;
}
}
Serialized with the following code
Person thePerson = new Person(){Name="Chris"};
XmlSerializer xs = new XmlSerializer(typeof(Person));
StringWriter sw = new StringWriter();
xs.Serialize(sw, thePerson);
Results in the followng XML - Notice there is no Age
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Chris</Name>
</Person>
Additionally to what Chris Taylor wrote: if you have something serialized as an attribute, you can have a property on your class named {PropertyName}Specified to control if it should be serialized. In code:
public class MyClass
{
[XmlAttribute]
public int MyValue;
[XmlIgnore]
public bool MyValueSpecified;
}
It exists a property called XmlElementAttribute.IsNullable
If the IsNullable property is set to true, the xsi:nil attribute is generated for class members that have been set to a null reference.
The following example shows a field with the XmlElementAttribute applied to it, and the IsNullable property set to false.
public class MyClass
{
[XmlElement(IsNullable = false)]
public string Group;
}
You can have a look to other XmlElementAttribute for changing names in serialization etc.
You can define some default values and it prevents the fields from being serialized.
[XmlElement, DefaultValue("")]
string data;
[XmlArray, DefaultValue(null)]
List<string> data;
I prefer creating my own xml with no auto-generated tags. In this I can ignore creating the nodes with null values:
public static string ConvertToXML<T>(T objectToConvert)
{
XmlDocument doc = new XmlDocument();
XmlNode root = doc.CreateNode(XmlNodeType.Element, objectToConvert.GetType().Name, string.Empty);
doc.AppendChild(root);
XmlNode childNode;
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
foreach (PropertyDescriptor prop in properties)
{
if (prop.GetValue(objectToConvert) != null)
{
childNode = doc.CreateNode(XmlNodeType.Element, prop.Name, string.Empty);
childNode.InnerText = prop.GetValue(objectToConvert).ToString();
root.AppendChild(childNode);
}
}
return doc.OuterXml;
}
In my case the nullable variables/elements were all String type. So, I simply performed a check and assigned them string.Empty in case of NULL. This way I got rid of the unnecessary nil and xmlns attributes (p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance)
// Example:
myNullableStringElement = varCarryingValue ?? string.Empty
// OR
myNullableStringElement = myNullableStringElement ?? string.Empty
private static string ToXml(Person obj)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
string retval = null;
if (obj != null)
{
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true }))
{
new XmlSerializer(obj.GetType()).Serialize(writer, obj,namespaces);
}
retval = sb.ToString();
}
return retval;
}
When using a standard .NET Xml Serializer, is there any way I can hide all null values? The below is an example of the output of my class. I don't want to output the nullable integers if they are set to null.
Current Xml output:
<?xml version="1.0" encoding="utf-8"?>
<myClass>
<myNullableInt p2:nil="true" xmlns:p2="http://www.w3.org/2001/XMLSchema-instance" />
<myOtherInt>-1</myOtherInt>
</myClass>
What I want:
<?xml version="1.0" encoding="utf-8"?>
<myClass>
<myOtherInt>-1</myOtherInt>
</myClass>
You can create a function with the pattern ShouldSerialize{PropertyName} which tells the XmlSerializer if it should serialize the member or not.
For example, if your class property is called MyNullableInt you could have
public bool ShouldSerializeMyNullableInt()
{
return MyNullableInt.HasValue;
}
Here is a full sample
public class Person
{
public string Name {get;set;}
public int? Age {get;set;}
public bool ShouldSerializeAge()
{
return Age.HasValue;
}
}
Serialized with the following code
Person thePerson = new Person(){Name="Chris"};
XmlSerializer xs = new XmlSerializer(typeof(Person));
StringWriter sw = new StringWriter();
xs.Serialize(sw, thePerson);
Results in the followng XML - Notice there is no Age
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Chris</Name>
</Person>
Additionally to what Chris Taylor wrote: if you have something serialized as an attribute, you can have a property on your class named {PropertyName}Specified to control if it should be serialized. In code:
public class MyClass
{
[XmlAttribute]
public int MyValue;
[XmlIgnore]
public bool MyValueSpecified;
}
It exists a property called XmlElementAttribute.IsNullable
If the IsNullable property is set to true, the xsi:nil attribute is generated for class members that have been set to a null reference.
The following example shows a field with the XmlElementAttribute applied to it, and the IsNullable property set to false.
public class MyClass
{
[XmlElement(IsNullable = false)]
public string Group;
}
You can have a look to other XmlElementAttribute for changing names in serialization etc.
You can define some default values and it prevents the fields from being serialized.
[XmlElement, DefaultValue("")]
string data;
[XmlArray, DefaultValue(null)]
List<string> data;
I prefer creating my own xml with no auto-generated tags. In this I can ignore creating the nodes with null values:
public static string ConvertToXML<T>(T objectToConvert)
{
XmlDocument doc = new XmlDocument();
XmlNode root = doc.CreateNode(XmlNodeType.Element, objectToConvert.GetType().Name, string.Empty);
doc.AppendChild(root);
XmlNode childNode;
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
foreach (PropertyDescriptor prop in properties)
{
if (prop.GetValue(objectToConvert) != null)
{
childNode = doc.CreateNode(XmlNodeType.Element, prop.Name, string.Empty);
childNode.InnerText = prop.GetValue(objectToConvert).ToString();
root.AppendChild(childNode);
}
}
return doc.OuterXml;
}
In my case the nullable variables/elements were all String type. So, I simply performed a check and assigned them string.Empty in case of NULL. This way I got rid of the unnecessary nil and xmlns attributes (p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance)
// Example:
myNullableStringElement = varCarryingValue ?? string.Empty
// OR
myNullableStringElement = myNullableStringElement ?? string.Empty
private static string ToXml(Person obj)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
string retval = null;
if (obj != null)
{
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true }))
{
new XmlSerializer(obj.GetType()).Serialize(writer, obj,namespaces);
}
retval = sb.ToString();
}
return retval;
}