user1437872 user1437872 - 3 months ago 23
C# Question

Using XmlAttributeOverrides on Nested Properties

I am trying to use XmlAttributeOverrides to control which class properties appear in the xml after the class has been serialized. It works on properties that are on the "root" class but not on nested properties. Here is a simple example to illustrate what I'm trying to accomplish.

My class hierarchy is as follows:

public class Main
{
public string Name { get; set; }
public Location Address { get; set; }
}

public class Location
{
public string StreetAddress { get; set; }
public Contact ContactInfo{ get; set; }
}

public class Contact
{
public string PhoneNumber { get; set; }
public string EmailAddr { get; set; }
}


When I serialize Main(), I get something like this:

<Main>
<Name></Name>
<Address>
<StreetAddress></StreetAddress>
<ContactInfo>
<PhoneNumber></PhoneNumber>
<EmailAddr></EmailAddr>
</ContactInfo>
</Address>
</Main>


What I am able to do is keep either Name or Address from appearing by using this:

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("Address"));
overrides.Add(typeof(Main), "Address", attribs);
xs = new XmlSerializer(typeof(Main), overrides);


What I need to also be able to do is keep Main.Address.ContactInfo from being serialized SOMETIMES (if it's empty). I tried the following but they didn't work:

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("ContactInfo "));
overrides.Add(typeof(Contact), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Contact), overrides);


and...

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("ContactInfo "));
overrides.Add(typeof(Main.Address.ContactInfo), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Main.Address.ContactInfo), overrides);


I've actually tried a lot more, including using XPath statements to designate the attribute name to target but didn't want to fill this page up with failed attempts. Is what I'm asking even possible by this method?

Answer

There are easier ways to achieve what you're looking for.

You said that what you are trying to achieve is to not serialize /Main/Address/ContactInfo if ContactInfo contains no data.

If you leave your code as is, it will serialize all of Main's properties, whether they are null or empty or not. The first step, is you need to add a XmlSerializerNamespaces property to all of your objects or each empty object will be serialized as <myElement xsi:nil="true" />. This can be accomplished easily, as follows:

public MyXmlElement
{
    public MyXmlElement()
    {
        // Add your own default namespace to your type to prevet xsi:* and xsd:*
        // attributes from being generated.
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            new XmlQualifiedName(string.Empty, "urn:myDefaultNamespace") });
    }

    [XmlElement("MyNullableProperty", IsNullable=false)]
    public string MyNullableProperty
    {
        get
        {
            return string.IsNullOrWhiteSpace(this._myNullableProperty) ? 
                null : this._myNullableProperty;
        }
        set { this._myNullableProperty = value; }
    }

    [XmlNamespacesDeclaration]
    public XmlSerializerNamespaces Namespaces { get { return this._namespaces; } }
    private XmlSerializerNamespaces _namespaces;
}

The code above declares a Namespaces property that holds all the relevant namespaces for the XML object. You should provide a default namespace for all of your objects (modeled after the code above). This prevents the xsi:* and xsd:* attributes from being output for your objects when they are serialized. Also, specify that the element is not nullable by using the System.Xml.Serialization.XmlElementAttribute.

Furthermore, by checking for string.IsNullOrWhiteSpace(someVariable) and returning null, then the property will not be serialized when the above has been done.

So, putting this all together for your Location class:

public class Location
{
    // You should have a public default constructor on all types for (de)sereialization.
    public Location()
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            new XmlQualifiedName(string.Empty, "urn:myNamespace"); // Default namespace -- prevents xsi:nil="true" from being generated, as well as xsd:* attributes.
        });
    }

    public string StreetAddress
    {
        // If you don't want <StreetAddress xsi:nil="true" /> to be generated, do this:
        get { return string.IsNullOrEmpty(this._streetAddress) ? null : this._streetAddress; }

        // Otherwise, if you don't care, just do
        // get;

        // Only need to implement setter if you don't want xsi:nil="true" to be generated.
        set { this._streetAddress = value; }

        // Otherwise, just
        // set;
    }
    private string _streetAddress;

    [XmlElement("ContactInfo", IsNullable=false)]
    public Contact ContactInfo
    {
        // You must definitely do this to prevent the output of ContactInfo element
        // when it's null (i.e. contains no data)
        get
        {
            if (this._contactInfo != null && string.IsNullOrWhiteSpace(this._contactInfo.PhoneNumber) && string.IsNullOrWhiteSpace(this._contactInfo.EmailAddr))
                return null;

             return this._contactInfo;
        }

        set { this._contactInfo = value; }
    }
    private Contact _contactInfo;

    [XmlNamespacesDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

With these changes to your Location class, the empty ContactInfo property should no longer be serialized to XML when none of the properties are null, empty, or whitespace, or if ContactInfo itself is null.

You should make similar changes to your other objects.

See my other stackoverflow answers for more on .NET XML serialization:

Comments