SYSK 274: How to Implement a Serializable Type with Read-Only Properties

There are times when you want to assure that two or more properties are always in-sync. Common examples include a class that represents id/name pair, e.g. Status with ID and Description properties, or Product class with product id and name…

 

The point is that you want to use defensive coding techniques to prevent a user of this class to change the id without changing the name or vice versa. A common way to handle this is to implement the ID and Name as read only properties and only allow these values to be changed as a pair either via the parameterized constructor or via provided SetProperties type of method (see below).

 

The challenge here is that with .NET default XmlSerializer (XmlType attribute used on the class) or DataContractSerializer (DataContract attribute used on the class), you will not be able to properly serialize (and thus de-serialize) the class (you’ll get default values instead of expected id and name because of no setter, a.k.a. modifier, properties).

 

What I use in these cases to achieve the expected outcome is to simply implement IXmlSerializable interface:

 

 

using Product = KeyValuePair<int, string>; // id-name pair

 

#region KeyValuePair

[XmlRoot(Namespace = "urn: YourCompany-com:v1")]

public class KeyValuePair<TKey, TValue> : System.Xml.Serialization.IXmlSerializable

{

    // NOTE: the reason we are not using .NET DataContractSerializer or XmlSerializer

    // is because they don't support read-only properties like id and name.

    // To assure that id and name are in sync, we've implemeneted SetProperties method.

    private TKey _key;

    private TValue _value;

    [XmlElement(Order = 1)]

    public TKey Key

    {

        get { return _key; }

    }

    [XmlElement(Order = 2)]

    public TValue Value

    {

        get { return _value; }

    }

    public KeyValuePair()

    {

        _key = default(TKey);

        _value = default(TValue);

    }

    public KeyValuePair(TKey key, TValue value)

    {

        _key = key;

        _value = value;

    }

    public void SetProperties(TKey key, TValue value)

    {

        _key = key;

        _value = value;

    }

    private System.Xml.XmlElement[] _extData;

    [XmlAnyElement(Order = 999999)]

    public System.Xml.XmlElement[] ExtensionData

    {

        get { return _extData; }

        set { _extData = value; }

    }

    #region IXmlSerializable Members

    public System.Xml.Schema.XmlSchema GetSchema()

    {

        throw new Exception("The method or operation is not implemented.");

    }

    public void ReadXml(System.Xml.XmlReader reader)

    {

        reader.MoveToAttribute("key");

        _key = (TKey) reader.ReadContentAs(typeof(TKey), null);

        reader.MoveToAttribute("value");

        _value = (TValue) reader.ReadContentAs(typeof(TValue), null);

    }

    public void WriteXml(System.Xml.XmlWriter writer)

    {

        writer.WriteStartAttribute("key");

        writer.WriteValue(_key);

        writer.WriteEndAttribute();

        writer.WriteStartAttribute("value");

        writer.WriteValue(_value);

        writer.WriteEndAttribute();

    }

    #endregion

}

#endregion