Getting null in nested elements when deserializing xml - c#

I'm deserializing my xml which is string, into my classes.
Problem is that part of nested child elements or their attributes return null.
Here is my deserialize function:
public static T DeSerialize<T>(this string xml)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (TextReader reader = new StringReader(xml))
{
T result = (T)serializer.Deserialize(reader);
return result;
}
}
Here are my classes:
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.specifiedcompany.com/API")]
public partial class VideoGames
{
private GameType[] typesField;
private Platform platformField;
private string memoField;
[System.Xml.Serialization.XmlArray("Types", Order = 0)]
[System.Xml.Serialization.XmlArrayItem("Data", IsNullable = false)]
public GameType[] Types
{
get
{
return this.typesField;
}
set
{
this.typesField= value;
}
}
[System.Xml.Serialization.XmlElementAttribute("Platform", Order = 1)]
public Platform Platform
{
get
{
return this.platformField;
}
set
{
this.platformField= value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Memo
{
get
{
return this.memoField;
}
set
{
this.memoField= value;
}
}
}
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class Platform
{
private decimal compressedField;
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal Compressed
{
get
{
return this.compressedField;
}
set
{
this.compressedField= value;
}
}
}
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class GameType
{
private Code[] codeField;
private string nameField;
[System.Xml.Serialization.XmlArrayAttribute("Code", Order = 0)]
[System.Xml.Serialization.XmlArrayItemAttribute("Category", IsNullable = false)]
public Code[] Code
{
get
{
return this.codeField;
}
set
{
this.codeField= value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Name
{
get
{
return this.nameField;
}
set
{
this.nameField= value;
}
}
}
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class Code
{
private string textField;
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Text
{
get
{
return this.textField;
}
set
{
this.textField= value;
}
}
}
Here is the xml:
<VideoGames Memo="1">
<Types>
<Data Name="RPG">
<Code>
<Category Text="TestingData"/>
</Code>
</Data>
</Types>
<Platform Compressed="3.2876"/>
</VideoGames>
And I'm using deserialize like this:
string xml = "";//xml string above.
VideoGames a = xml.DeSerialize<VideoGames>();
I expect every property in class will have value, but only Memo in VideoGames has value, others are null.
For example: a.Types is null, but a.Memo is "1".
I have tried all related questions searched by this topic, and none of them work.
Note:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xml);
I have checked this xDoc, it loaded perfectly, nothing was lost.

The easiest way to diagnose problems in XML deserialization is to serialize to XML and compare the observed XML with the required XML. Usually the problem will be immediately apparent.
If I create and serialize an instance of VideoGames as follows:
var root = new VideoGames
{
Types = new []
{
new GameType
{
Name = "RPG",
Code = new []
{
new Code { Text = "TestingData" },
}
},
},
Platform = new Platform { Compressed = 3.2876m, },
};
var xml = root.GetXml(omitStandardNamespaces: true);
Console.WriteLine(xml);
Using the following extension method:
public static string GetXml<T>(this T obj, XmlSerializer serializer = null, bool omitStandardNamespaces = false)
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
{
ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
}
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
Then the output is:
<VideoGames>
<Types xmlns="http://www.specifiedcompany.com/API">
<Data Name="RPG">
<Code>
<Category Text="TestingData" />
</Code>
</Data>
</Types>
<Platform Compressed="3.2876" xmlns="http://www.specifiedcompany.com/API" />
</VideoGames>
Demo fiddle here.
As you can see, the child elements <Types> and <Platform> are all serialized in an XML namespace, specifically http://www.specifiedcompany.com/API.
Your deserialization fails because, in the sample XML, these elements are not in any namespace.
Why is this happening? It's because all your classes including VideoGames have an XmlTypeAttribute attribute applied that specifies a Namespace:
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.specifiedcompany.com/API")]
public partial class VideoGames
{
}
What this attribute does is to specify that by default, all properties of the type should be serialized into the specified namespace. (Compare with XmlRootAttribute which specifies how the root element <VideoGames> itself, rather than just its children, is to be serialized.)
If you don't want that, remove the XmlTypeAttribute.Namespace setting from your c# classes VideoGames, Platform, GameType and Code (demo fiddle #2 here). If you do want that, modify your XML to include the required namespace as shown in the output XML in this answer.

I ran into this same issue recently, where it turns out if a single field in a nested XML object was null or missing, or the value in the field couldn't deserialize to the specific type, the List representing the nested elements would just return null.
I tried all sorts of attributes for the fields, trying IsNullable and a bunch of other recommended attributes as mentioned here on stackoverflow, nothing helped.
To fix this, I ended up changing all the fields in my class the XML is being deserialized to type of string.
Hope this helps someone.

Related

XML element name with special characters while serialising

While serialising the object to the XML I use the attribute convention like [XmlElement("MyData:Pool1")] for me it does the job but the XML looks like
<_x005C_MyData_x003A_Pool1 >
I presume it convert to the colon or any special chars to some other formats, I tried changing with backslash, # and $ signs preceding to the string, but it didn't helped me.
Any suggestions apart from using string / regex replace ?
private void Serlise(Interface request)
{
var xsSubmit = new XmlSerializer(typeof(Interface));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
var xml = "";
using (var encoder = new CustomEncoder())
{
using (var writer = XmlWriter.Create(encoder))
{
xsSubmit.Serialize(writer, request,ns);
xml = encoder.ToString();
}
}
File.WriteAllText(#"output.xml", xml);
}
Below is the class to be serialised
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.foo1.com/bar/test/")]
[System.Xml.Serialization.XmlRootAttribute(ElementName = "Pool", Namespace = "http://www.foo1.com/bar/test/", IsNullable = false)]
public partial class Root
{
private Tester adminField;
private string versionField;
[XmlElement("Test:Pool1")]
public Tester Admin
{
get
{
return this.adminField;
}
set
{
this.adminField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
}
}
}
NOTE : Slightly adjusted namespace & class name on XML due to NDA
<?xml version="1.0" encoding="utf-8"?><q1:MydataPool Version="1" xmlns:q1="http://www.foo1.com/bar/test"><q1:Data Id ="000123" Function="Hi"><q1:Hello Test="Abcd" /></q1:Data></q1:MydataPool>
Specify the namespace in the property attribute:
[XmlElement("Pool1", Namespace = "url")]
public Tester Admin
Set a prefix for this namespace:
var ns = new XmlSerializerNamespaces();
ns.Add("MyData", "url");
In the result, you will get
<MyData:Pool1>

XML changes after Serialization

I noticed that my xml changes after serialization. e.g.
* the ns in the start of the Message element disappears
* xmlns:ns attribute becomes xmlns
* there are new attributes added in Message element - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" and xmlns:xsd="http://www.w3.org/2001/XMLSchema"
* new attribute in Header element - xmlns
How can I keep the original form of the xml and prevent these attributes from being added?
Here's how the original XML looks like:
<?xml version="1.0" encoding="UTF-8"?>
<ns:Message xmlns:ns="http://example.com">
<Header version="1.0">
<Sender>3015207400109</Sender>
<Receiver>8711200999903</Receiver>
<MessageID>000D2613F64AC021ED783C084735EC78E53</MessageID>
<CreationDateTime>2017-03-21T08:00:47Z</CreationDateTime>
</Header>
</ns:Message>
And here's the xml after it has been serialized:
<?xml version="1.0" encoding="UTF-8"?>
<Message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://example.com">
<Header version="1.0" xmlns="">
<Sender>3015207400109</Sender>
<Receiver>8711200999903</Receiver>
<MessageID>000D2613F64AC021ED783C084735EC78E53</MessageID>
<CreationDateTime>2017-03-21T08:00:47Z</CreationDateTime>
</Header>
</Message>
The code below is the (generated) class that represents the Message xml.
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://example.com")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://example.com", IsNullable = false)]
public partial class Message
{
private Header headerField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Namespace = "")]
public Header Header
{
get
{
return this.headerField;
}
set
{
this.headerField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Header
{
private ulong senderField;
private ulong receiverField;
private string messageIDField;
private System.DateTime creationDateTimeField;
private decimal versionField;
/// <remarks/>
public ulong Sender
{
get
{
return this.senderField;
}
set
{
this.senderField = value;
}
}
/// <remarks/>
public ulong Receiver
{
get
{
return this.receiverField;
}
set
{
this.receiverField = value;
}
}
/// <remarks/>
public string MessageID
{
get
{
return this.messageIDField;
}
set
{
this.messageIDField = value;
}
}
/// <remarks/>
public System.DateTime CreationDateTime
{
get
{
return this.creationDateTimeField;
}
set
{
this.creationDateTimeField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
}
}
}
The two XML files you show are semantically identical. Thus, I'd recommend not worrying about the fact that XmlSerializer inserts XML standard namespaces or chooses a different prefixing scheme than was used in the original file.
If, for whatever reason, you must suppress output of the standard namespaces and must preserve the prefixing scheme of the original file, here's what you can do.
Firstly, to omit the xsi and xsd namespaces at the root level, follow the instructions from Omitting all xsi and xsd namespaces when serializing an object in .NET?:
var s = new XmlSerializer(objectToSerialize.GetType());
var ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);
Next, in order to keep the original form of the xml you must first somehow capture the actual XML namespaces and prefixes encountered while reading the file and save them in the Message class somewhere for reuse later. Luckily XmlSerializer does support this: you can add an XmlSerializerNamespaces valued public property or field to Message and mark it with [XmlNamespaceDeclarations]. This member will now capture namespaces encountered during deserialization, and cause those namespaces to be added back during serialization.
Putting these two ideas together, you can modify your Message type as follows:
public partial class Message
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces XmlFileNamespaces { get; set; }
/// <summary>
/// returns a XmlSerializerNamespaces to use when serializing a Message as the root XML object.
/// If Message was previously deserialized from XML, the actual namespaces observed will be returned.
/// Otherwise, a default will be returned that suppresses output of the xmlns:xsi and xmlns:xsd namespace attributes.
/// </summary>
[XmlIgnore]
public XmlSerializerNamespaces XmlRootNamespaces
{
get
{
if (XmlFileNamespaces != null)
return XmlFileNamespaces;
var xmlNamespaces = new XmlSerializerNamespaces();
xmlNamespaces.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
// xmlNamespaces.Add("ns", "http://example.com"); // Or, if you prefer, add this namespace as well as disabling xmlns:xsi and xmlns:xsd.
return xmlNamespaces;
}
}
}
And serialize from and to XML as follows:
var message = xml.LoadFromXml<Message>();
var reserializedXml = message.GetXml(message.XmlRootNamespaces);
Using the following extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
}
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns)
{
using (var textWriter = new Utf8StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
// http://stackoverflow.com/questions/3862063/serializing-an-object-as-utf-8-xml-in-net
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
}
Prototype fiddle.

XML deserialization in C# returns an empty object

I have a problem here about deserialization. I have a XML file that I need to deserialize into a class that I get from a Service reference. I know how to deserialize a XML file, but when I try to deserialize this file I get a empty class object. I don't understand why is it doing that.
The XML files content looks like this:
<?xml version="1.0" encoding="UTF-8" ?><iVAZFile xmlns="http://www.v.lt/c/i/iv">
<FileDescription>
<FileVersion>i1.3.3</FileVersion>
<FileDateCreated>2016-11-07T12:28:32</FileDateCreated>
<SoftwareCompanyName>otechnika"</SoftwareCompanyName>
<SoftwareName>Eita</SoftwareName>
<SoftwareVersion>2016.9</SoftwareVersion>
<CreatorRegistrationNumber>123060356</CreatorRegistrationNumber>
</FileDescription>
</iVAZFile>
The class looks like this:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1590.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.v.lt/c/i/iv")]
public partial class FileDescription : object, System.ComponentModel.INotifyPropertyChanged {
private string fileVersionField;
private System.DateTime fileDateCreatedField;
private string softwareCompanyNameField;
private string softwareNameField;
private string softwareVersionField;
private ulong creatorRegistrationNumberField;
public FileDescription() {
this.fileVersionField = "iVAZ1.3.3";
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public string FileVersion {
get {
return this.fileVersionField;
}
set {
this.fileVersionField = value;
this.RaisePropertyChanged("FileVersion");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public System.DateTime FileDateCreated {
get {
return this.fileDateCreatedField;
}
set {
this.fileDateCreatedField = value;
this.RaisePropertyChanged("FileDateCreated");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public string SoftwareCompanyName {
get {
return this.softwareCompanyNameField;
}
set {
this.softwareCompanyNameField = value;
this.RaisePropertyChanged("SoftwareCompanyName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public string SoftwareName {
get {
return this.softwareNameField;
}
set {
this.softwareNameField = value;
this.RaisePropertyChanged("SoftwareName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=4)]
public string SoftwareVersion {
get {
return this.softwareVersionField;
}
set {
this.softwareVersionField = value;
this.RaisePropertyChanged("SoftwareVersion");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=5)]
public ulong CreatorRegistrationNumber {
get {
return this.creatorRegistrationNumberField;
}
set {
this.creatorRegistrationNumberField = value;
this.RaisePropertyChanged("CreatorRegistrationNumber");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
And the method that I use to populate the class:
using (MemoryStream memoryStream = new MemoryStream())
{
using (BinaryWriter binWriter = new BinaryWriter(memoryStream, Encoding.UTF8))
{
string filep = File.ReadAllText("test.xml");
binWriter.Write(filep);
memoryStream.Seek(2, SeekOrigin.Begin);
using (StreamReader streamReader = new StreamReader(memoryStream))
{
using (XmlReader reader = XmlReader.Create(streamReader))
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "FileDescription";
xRoot.IsNullable = false;
XmlSerializer serializer = new XmlSerializer(typeof(FileDescription),xRoot);
FileDescription dsc = new FileDescription();
dsc=(FileDescription)serializer.Deserialize(reader);
}
}
}
}
A few additional comment to say what I also tried:
I made my own class that looked the same as the one from the service reference and added a new parameter [XmlRoot(ElementName="FileDescription"),XmlType("FileDescription")]
then the deserilization worked on my class.
Tried adding values by hand without deserilization that worked fine as well so there's no problem with the service code.
Also when I'm deserializing the xml I remove <iVAZFile xmlns="http://www.v.lt/c/i/iv"> and </iVAZFIle> because when deserializing it will throw and exception was not expecting .
And lastly a few words I cannot change the service reference class and I need to use that class for later so I cannot make my own.
The difference is the namespace - per the XmlType attribute on your FileDescription class, the namespace is http://www.v.lt/c/i/iv. This applies to all child elements of the FileDescription. The root namespace is whatever you configure via your XmlRootAttribute or by the constructor that takes a default namespace.
As I suggested, the easiest way to debug this sort of issue is to do the reverse - serialise an object and see what it looks like. As you can see in this fiddle the output at present would look like this. This is why you're not getting any error (as the root class matches), but all the child elements in the XML you're deserialising are in the wrong namespace.
<FileDescription>
<FileVersion xmlns="http://www.v.lt/c/i/iv">iVAZ1.3.3</FileVersion>
<FileDateCreated xmlns="http://www.v.lt/c/i/iv">0001-01-01T00:00:00</FileDateCreated>
<CreatorRegistrationNumber xmlns="http://www.v.lt/c/i/iv">0</CreatorRegistrationNumber>
</FileDescription>
If you specify the same namespace for the root as in this fiddle, the output looks like this:
<FileDescription xmlns="http://www.v.lt/c/i/iv">
<FileVersion>iVAZ1.3.3</FileVersion>
<FileDateCreated>0001-01-01T00:00:00</FileDateCreated>
<CreatorRegistrationNumber>0</CreatorRegistrationNumber>
</FileDescription>
You need to keep the namespace in your amended document. You can see this works in this fiddle.
(Posted on behalf of the OP).
The problem is solved thanks to Charles Mager. The problem was I had to add the namespace to every value in my XML so it would have to look like this:
<FileDescription>
<FileVersion xmlns="http://www.v.lt/c/i/iv">i1.3.3</FileVersion>
<FileDateCreated xmlns="http://www.v.lt/c/i/iv">2016-11-07T12:28:32</FileDateCreated>
<SoftwareCompanyName xmlns="http://www.v.lt/c/i/iv">otechnika"</SoftwareCompanyName>
<SoftwareName xmlns="http://www.v.lt/c/i/iv">Eita</SoftwareName>
<SoftwareVersion xmlns="http://www.v.lt/c/i/iv">2016.9</SoftwareVersion>
<CreatorRegistrationNumber xmlns="http://www.v.lt/c/i/iv">123060356</CreatorRegistrationNumber>
</FileDescription>
Now it works fine, thanks for the help.

Can I have null attribute and other attribute at the same tag in XML created by XSD C# generated class?

I have a bunch of C# classes, which are auto generated from an XSD. Then I generate XML files based on those C# classes. Nothing existing so far.
The problem:
The generated XML files are going through validation and the validation requires an extra attribute to all XML tags with xsi:nil="true". Basically the tags should look like : <testTag.01 xsi:nil="true" NV="123123" />, but I can't achieve that in C#. My code is:
if (myObject.TestTag.HasValue)
{
t.testTag01 = new testTag01();
t.testTag01.Value = myObject.TestTag.Value;
}
//else
//{
// t.testTag01 = new testTag01();
// t.testTag01.NV = "123123";//Not Recorded
//}
This code generates <testTag.01>SomeValue</testTag.01> or <testTag.01 xsi:nil="true"/>.
If I uncomment the ELSE, the result would be: <testTag.01>SomeValue</testTag.01> or <testTag.01 NV="123123" />.
So I have no idea how to get to the format, which is required by the validation tool. Any ideas ?
P.S.
Here is the auto-generated C# class:
/// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd",
"4.0.30319.33440")] [System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true,
Namespace="http://www.blabla.org")]
public partial class testTag01 {
private string nvField;
private SomeEnum valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string NV {
get {
return this.nvField;
}
set {
this.nvField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public SomeEnum Value {
get {
return this.valueField;
}
set {
this.valueField = value;
}
} }
I wouldn't like to alter that part, but I understand it is impossible without doing it. Also I have tried to set SomeEnum to be Nullable. public SomeEnum? Value, but is throwing an exception:
Cannot serialize member 'Value' of type System.Nullable`1[]. XmlAttribute/XmlText cannot be used to encode complex types.
XmlSerializer doesn't directly support binding to elements that simultaneously have xsi:nil="true" along with other attribute values; see Xsi:nil Attribute Binding Support: The nil attribute and other attributes.
Thus, you need to emit the attribute manually.
If you want to be able to generate an element with no content and two attributes, one named NV and the other always being xsi:nil="true", you can modify your testTag01 class to have the NV property as well as a synthetic property having the correct namespace and name:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return "true"; } set { } }
}
If you sometimes want to have xsi:nil="true" but at other times want the element to have content corresponding to your SomeEnum, you need to do something a bit more complicated, since the xsi:nil="true" must be suppressed when the element has content:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return SomeEnum == null ? "true" : null; } set { } }
public bool ShouldSerializeNil() { return SomeEnum == null; }
[XmlIgnore]
public SomeEnum? SomeEnum { get; set; }
[XmlText]
public string SomeEnumText
{
get
{
if (SomeEnum == null)
return null;
return SomeEnum.Value.ToString();
}
set
{
// See here if one needs to parse XmlEnumAttribute attributes
// http://stackoverflow.com/questions/3047125/retrieve-enum-value-based-on-xmlenumattribute-name-value
value = value.Trim();
if (string.IsNullOrEmpty(value))
SomeEnum = null;
else
{
try
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, false);
}
catch (Exception)
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, true);
}
}
}
}
}
(An element that simultaneously has both xsi:nil="true" and content would be a violation of the XML standard; hopefully you don't have that.)
Then use it like:
public class TestClass
{
[XmlElement("testTag.01")]
public testTag01 TestTag { get; set; }
public static void Test()
{
Test(new TestClass { TestTag = new testTag01 { NV = "123123" } });
Test(new TestClass { TestTag = new testTag01 { NV = "123123", SomeEnum = SomeEnum.SomeValue } });
}
private static void Test(TestClass test)
{
var xml = test.GetXml();
var test2 = xml.LoadFromXML<TestClass>();
Console.WriteLine(test2.GetXml());
Debug.WriteLine(test2.GetXml());
if (test2.TestTag.NV != test.TestTag.NV)
{
throw new InvalidOperationException("test2.TestTag.NV != test.TestTag.NV");
}
}
}
The XML output looks like:
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123" xsi:nil="true" />
</TestClass>
Or
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123">SomeValue</testTag.01>
</TestClass>
Prototype fiddle using these extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serializer = null)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns = null, XmlWriterSettings settings = null, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
settings = settings ?? new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(typeof(T))).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
As expected there is no solution for that case out of the box, so I improvise a bit and achieved my goal in a post processing logic.
I am parsing the generated XML and if I am looking for a node with xsi:nil attribute, but without NV attribute - I add NV attribute with default value.
Same for the nodes with NV attribute, but no xsi:nil.
Here is the code:
XmlDocument doc = new XmlDocument();// instantiate XmlDocument and load XML from file
doc.Load("somepath.xml");
//Get the nodes with NV attribute(using XPath) and add xsi:nill to that nodes
XmlNodeList nodes = doc.SelectNodes("//*[#NV]");
foreach (XmlNode node in nodes)
{
XmlAttribute nilAttr = doc.CreateAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance");
nilAttr.Value = "true";
node.Attributes.Append(nilAttr);
}
//Get the nodes with xsi:nill attribute(using XPath) and add NV with default value to that nodes
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
XmlNodeList nilNodes = doc.SelectNodes("//*[#xsi:nil]", nsManager);
foreach (XmlNode node in nilNodes)
{
XmlAttribute nvAttr = doc.CreateAttribute("NV");
nvAttr.Value = "7701003";
node.Attributes.Append(nvAttr);
}
doc.Save("somepath.xml");
The upper answer makes totally sense, but since these classes are auto-generated I will do it my way with the post processing, cause if the provider changes the XSD schema, my solution doesn't need any extra work. Thanks anyway.

Get attribute values to the XML-File

I got a issue with XML, and write some information on a XML-file.
I got several xsd-files describing my XML. I created one big .cs-file with "xsd/c testfile1.xsd testFile2.xsd..." etc. And everything went nice and looks good.
But if I take one created class i.e. testfile1.xsd, it looks like
"<xs:complexType name="Header">" and inside that one there is this some ordinary xs:element and stuff, but also this: "<xs:attribute name="version" default="1.0.0.0"/>". This is translated to:
"public Header() {
this.versionField = "1.0.0.0";}"
in the generated class 'Header'. And it got as well this field: private string versionField;
. (There is of course a couple of other private fields as well, but those works good.). So I create instances of all classes, fill them with data and write it as an XML-file with this:
- XmlSerializer XmlSerRoot = new XmlSerializer(typeof(RootInformation))
(the root of my xml!)
- StringWriterWithEncoding strWriter = new StringWriterWithEncoding(Encoding.GetEncoding("iso-8859-1"));
- XmlDocument documentInXML = new XmlDocument();
- XmlSerRoot.Serialize(strWriter, rootInformation); (Here is the XML, filled with values, and the Header.version got the value 1.0.0.0)
- string XmlString;
- XmlString = strWriter.ToString(); (Here I can look at the created xml when debugging in VS and now the version-information is gone)
- documentInXML.LoadXml(XmlString);
- documentInXML.Save(myPath);
But when I look at the xml-file this <Header> is not have anything like version. I want i to look like: <Header version="1.0.0.0">
I even tried to do it in code like:
Header header = new Header();
header.version = header.version;
and
with header.version = "1.0.0.0";
But still it's no version-text in the tag. All other tags got their value. Just this <Header> loose this extra information.
Does someone got a tip? There is a lot of places I need this to work. Everything else is just working fine.
Regards, /E
Here is an piece of example-code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
namespace TestAppXML
{
class Program
{
static void Main(string[] args)
{
RootInfo rootInfo = new RootInfo();
rootInfo.RootText = "This is the Root!";
Header header = new Header();
header.TestHeader = "This is HeaderText!";
rootInfo.Header = header;
XmlSerializer XmlSerRoot = new XmlSerializer(typeof(RootInfo));
StringWriterWithEncoding strWriter = new StringWriterWithEncoding(Encoding.GetEncoding("iso-8859-1"));
XmlDocument documentInXML = new XmlDocument();
XmlSerRoot.Serialize(strWriter, rootInfo);
string XmlString;
XmlString = strWriter.ToString();
documentInXML.LoadXml(XmlString);
strWriter.Close();
documentInXML.Save(#"C:\TestXml.xml");
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://acme.com")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://acme.com", IsNullable = false)]
public partial class RootInfo
{
private Header headerField;
private string rootTextField;
public Header Header
{
get { return this.headerField; }
set { this.headerField = value; }
}
[System.Xml.Serialization.XmlElementAttribute(DataType = "normalizedString")]
public string RootText
{
get { return this.rootTextField; }
set { this.rootTextField = value; }
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://acme.com")]
public partial class Header
{
private string testHeaderField;
private string versionField;
public Header()
{
this.versionField = "1.0.0.0";
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(DataType = "normalizedString")]
public string TestHeader
{
get { return this.testHeaderField; }
set { this.testHeaderField = value; }
}
[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute("1.0.0.0")]
public string version
{
get { return this.versionField; }
set { this.versionField = value; }
}
}
class StringWriterWithEncoding : StringWriter
{
private Encoding MyEncoding;
public StringWriterWithEncoding(Encoding encoding)
: base()
{MyEncoding = encoding;}
public override Encoding Encoding
{
get{return MyEncoding;}
}
}
}
Nevermind, I think I knov the issue. It's because this xsd.exe creates 'DefaultValueAttribute' for those fields. That prevent this field to be in the xml. Maybe some xsd-switch could have done that, I dunno...

Categories