In C#, when processing XML that contains elements that are defined in schema as both nillable="true"
and minOccurs="0"
, what is the most elegant solution to tell a nil element from an omitted element?
The use case is a situation where a service receives an XML fragment containing elements representing all the fields of a record that have changed, but none of the fields that have not changed.
For example, when the record changes from { a: 1; b: 2; c: 3; }
to { a: 1; b: 4; c: null }
, the service might receive:
<change>
<b>4</b>
<c xsi:nil="true" />
</change>
When the record changes from { a: 1; b: 2; c: 3; }
(identical) to { a: 1; b: 4; c: 3 }
(no change for 'c'), the service might receive:
<change>
<b>4</b>
</change>
However, in C# these two fragments with different meaning both map to an object which looks like { a: null; b: 4; c: null; }
. When parsing the XML, the information about c being explicitly nil or simply absent is lost. We're unsure for both a and b whether they should be set to null, or left untouched.
In this example you might suggest the message should include all the fields to avoid the confusion (as well as something to identify the record being changed), but we're dealing with actual messages about huge records where the need to only send the actual is relevant. And we're dealing with more than just integer fields, but all kinds of simple and complex types.
I consider the XML fragments to be fairly elegant and clear, but what is the most elegant and clear solution you would suggest when processing them in a C# application?
Assuming you are using XmlSerializer
, you can add an extra Boolean property to remember whether a property has been explicitly set. Additionally, if the property has the name XXXSpecified
where XXX
is the name of the related "real" property, then XmlSerializer
will omit the property on serialization. For example:
public class TestClass
{
string _value = null;
[XmlElement("Value", IsNullable=true)]
public string Value
{
get { return _value; }
set
{
_value = value;
ValueSpecified = true;
}
}
[XmlIgnore]
public bool ValueSpecified { get; set; }
public static void Test()
{
Test(new TestClass());
Test(new TestClass() { Value = null });
Test(new TestClass() { Value = "Something" });
}
static void Test(TestClass test)
{
var xml = test.GetXml();
Debug.WriteLine(xml);
var testBack = xml.LoadFromXML<TestClass>();
Debug.Assert(testBack.Value == test.Value && testBack.ValueSpecified == test.ValueSpecified);
}
}
The XML output for the three test cases is:
<TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> <TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Value xsi:nil="true" /> </TestClass> <TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Value>Something</Value> </TestClass>
As you can see, the distinction between a null property and an unset property is successfully serialized and deserialized.
For more information, see here: MinOccurs Attribute Binding Support . (The documentation describes support for public fields but the functionality works for public properties as well.)