dennismeister
dennismeister

Reputation: 61

Deserialize object with XmlAnyElementAttribute not working

Have class generated from svcutil with anyField property:

[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "4.6.1055.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:epcglobal:epcis:xsd:1")]
public partial class ErrorDeclarationExtensionType
{

    private System.Xml.XmlElement[] anyField;

    private System.Xml.XmlAttribute[] anyAttrField;

    /// <remarks/>
    [System.Xml.Serialization.XmlAnyElementAttribute(Namespace = "", Order = 0)]
    public System.Xml.XmlElement[] Any
    {
        get
        {
            return this.anyField;
        }
        set
        {
            this.anyField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAnyAttributeAttribute()]
    public System.Xml.XmlAttribute[] AnyAttr
    {
        get
        {
            return this.anyAttrField;
        }
        set
        {
            this.anyAttrField = value;
        }
    }
}

Create simple program that will serialize object to string and then deserialize this string back to object:

static void Main(string[] args)
{
    var example = new ErrorDeclarationExtensionType();
    var doc = new XmlDocument();
    doc.LoadXml("<testXML2 xmlns=\"urn:testXML2\">anyContents0</testXML2>");
    var test = doc.DocumentElement;
    var array = new XmlElement[]
    {
        test
    };
    example.Any = array;

    var sw = new StringWriter();
    XmlTextWriter tw = null;

    var xmlSerializer = new XmlSerializer(example.GetType());
    tw = new XmlTextWriter(sw);
    xmlSerializer.Serialize(tw, example);

    Console.WriteLine(sw.ToString());
    Console.ReadKey();
    ErrorDeclarationExtensionType errorDeclaration;

    var xmlSer = new XmlSerializer(typeof(ErrorDeclarationExtensionType));

    using (TextReader tr = new StringReader(sw.ToString()))
    {
        errorDeclaration = (ErrorDeclarationExtensionType)xmlSer.Deserialize(tr);
    }
    Console.ReadKey();
}

And after deserializing get object with all fields equals null. Guess that is problem with XmlAnyElementAttribute but didn't get any solution for this. Actually this class was a property of other class that used in prod, but have the same error in this situation.

Upvotes: 1

Views: 592

Answers (1)

dbc
dbc

Reputation: 117164

Your problem is that you have set XmlAnyElementAttribute.Namespace inconsistently with your XML:

[System.Xml.Serialization.XmlAnyElementAttribute(Namespace = "", Order = 0)]
public System.Xml.XmlElement[] Any 
{
    get
    {
        return this.anyField;
    }
    set
    {
        this.anyField = value;
    }
}

By setting this property, you indicate that only unknown elements that are not in a namespace can be bound to the Any property. However, the unknown element in your sample XML is in some other namespace, specifically "urn:testXML2". If I change your sample XML to not include a namespace, e.g.

<ErrorDeclarationExtensionType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <testXML2>anyContents0</testXML2>
</ErrorDeclarationExtensionType>

Then Any is populated successfully. Demo fiddle #1 here.

Similarly, if I change the namespace in the metadata attribute to "urn:testXML2" then Any is populated:

[System.Xml.Serialization.XmlAnyElementAttribute(Namespace = "urn:testXML2", Order = 0)]
public System.Xml.XmlElement[] Any

Demo fiddle #2 here.

And if I remove XmlAnyElementAttribute.Namespace then either can be bound successfully:

[System.Xml.Serialization.XmlAnyElementAttribute(Order = 0)]
public System.Xml.XmlElement[] Any
{
    get
    {
        return this.anyField;
    }
    set
    {
        this.anyField = value;
    }
}

Demo fiddle #3 here.

This is explained somewhat incorrectly in the documentation remarks for XmlAnyElementAttribute:

When serialized... if you set the Namespace property value, you must set the Name property as well, and the XmlElement or XmlNode objects must also have the same name and namespace values.

In fact, setting Namespace without Name does not seem to cause an error in .NET Core 3.1.5 during serialization, and during deserialization, binds to all unknown elements with that specific namespace.

Notes:

  • You may want to replace use of XmlTextWriter with XmlWriter as the former has been deprecated since .Net 2.0:

    var sw = new StringWriter();
    using (var tw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true /* or false if you prefer*/ }))
    {
        xmlSerializer.Serialize(tw, example);
    }
    
  • You have set XmlAnyElementAttribute.Order = 0:

    Gets or sets the explicit order in which the elements are serialized or deserialized.

    Once the Order property has been used on one public property or field in a type, it must be applied to all public properties and fields for that type and all inherited types.

    Thus your unknown element would need to appear before any known elements to be deserialized successfully -- although, in this case, your class has no additional elements, and setting Order is not necessary.

Upvotes: 2

Related Questions