Kjara
Kjara

Reputation: 2912

serialize default attributes using XmlSerializer

Based on this post I tried to create a child class of XmlTextWriter that writes all attribute values, even the default ones. But I can't make it work. Can someone help?

Here is the class I want to serialize:

[System.SerializableAttribute()]
[System.Xml.Serialization.XmlType(AnonymousType = true)]
[System.Xml.Serialization.XmlRoot(Namespace = "", IsNullable = false)]
public class Foo
{
    public string content { get; set; }

    [System.Xml.Serialization.XmlAttribute()]
    [System.ComponentModel.DefaultValue(true)]
    public bool isActive { get; set; }
}

Here is the code where I create an instance of Foo and serialize it to a string:

XmlSerializerNamespaces ns = new XmlSerializerNamespaces(
    new XmlQualifiedName[] { new XmlQualifiedName("", "") });
    // I need that for not writing a namespace during serialization
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
Foo f = new Foo();
f.content = "hello";
f.isActive = false;

string fStr;
using (MemoryStream ms = new MemoryStream())
using (XmlWriter wr = new XmlTextWriter(ms, new UTF8Encoding(false)))
{
    serializer.Serialize(wr, f, ns);
    ms.Position = 0;
    using (StreamReader sr = new StreamReader(ms))
    {
        fStr = sr.ReadToEnd();
    }
}

This code will give me the following content for fStr:

<?xml version="1.0" encoding="utf-8"?><Foo><content>hello</content></Foo>

But I want to get the default value explicitly written as well, i.e.

<?xml version="1.0" encoding="utf-8"?><Foo isActive="true"><content>hello</content></Foo>

So I created a child class of XmlTextWriter and replaced the new XmlTextWriter(...) from above with new DefaultValueXmlTextWriter(...). Here is my DefaultValueXmlTextWriter class:

public class DefaultValueXmlTextWriter : XmlTextWriter
{

    public DefaultValueXmlTextWriter(Stream s, Encoding e) : base(s, e) { }
    public DefaultValueXmlTextWriter(string s, Encoding e) : base(s, e) { }
    public DefaultValueXmlTextWriter(TextWriter t) : base(t) { }

    public override void WriteAttributes(XmlReader reader, bool defattr)
    {
        base.WriteAttributes(reader, true);
    }
}

The mousover for base.WriteAttributes says:

When overridden in a derived class, writes out all the attributes found at the current position in the XmlReader.

So I assumed that always choosing true for defattr would do the trick, but it does not change anything in the output. I tried false as well, doesn't work either.

Any ideas?

Upvotes: 0

Views: 387

Answers (1)

Van Amburg
Van Amburg

Reputation: 1237

In your example you are mixing ComponentModel and Serialization annotations and they don't appear to work as expected. I ran through every permutation settings I could think of and the results I got were unexpected.

var foo = new Foo();
foo.content = "hello";
//foo.isActive is left undefined
Console.WriteLine($"foo.isActive is left undefined");
Console.WriteLine($"foo.isActive = {foo.isActive}");
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
string file = "File.xml";
using (StreamWriter sw = new StreamWriter(file))
{
    serializer.Serialize(sw,foo);
}
bool attExists = File.ReadAllText(file).Contains("isActive");
Console.WriteLine($"It is {attExists} that isActive exists in {file}");
using (StreamReader sr = new StreamReader(file))
{
    Foo test = (Foo)serializer.Deserialize(sr); 
    Console.WriteLine(test.isActive);
}

The results where the 3rd line of the code is changed and reflected in the output of the first line the test

foo.isActive is left undefined
foo.isActive = False
It is True that isActive exists in File.xml
False

foo.isActive = true;
foo.isActive = True
It is False that isActive exists in File.xml
False

foo.isActive = false;
foo.isActive = False
It is True that isActive exists in File.xml
False

If you are hydrating your results back into a different class your results here are not going to be so good. It would appear that all the annotation does is tell the serializer not output the value if it is the default value.

Personally, I would accomplish this by defaulting the value in a public constructor and ignoring the annotation all together. The intent is more explicit and re-engineering the Xmlserializer is way, way overkill and may come with other unintended consequences.

[System.SerializableAttribute()]
[System.Xml.Serialization.XmlType(AnonymousType = true)]
[System.Xml.Serialization.XmlRoot(Namespace = "", IsNullable = false)]
public class Foo
{
    public string content { get; set; }
    [System.Xml.Serialization.XmlAttribute()]
    public bool isActive { get; set; }
    public Foo()
    {
        isActive = true;
    }
}

Running the same test results in much more predictable results.

foo.isActive is left undefined;
foo.isActive = True
It is True that isActive exists in File.xml
True

foo.isActive = true;
foo.isActive = True
It is True that isActive exists in File.xml
True

foo.isActive = false;
foo.isActive = False
It is True that isActive exists in File.xml
False

Good luck!

Upvotes: 1

Related Questions