Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

...

Child pages (Children Display)

All classes deriving from ModelBase use the serialization engine of Catel to serialize itself in a whole or as a subset of properties. Below is a schema which sheds some light on the architecture.

...

Info

Workflow 1 represents the serialization. Workflow 2 represents the deserialization.

Customizing serialization

Customizing the serialization for specific models

Catel has a default behavior for what gets serialized. It can be tweaked by including / excluding fields and properties by using the IncludeInSerialization and ExcludeFromSerialization attributes. But sometimes one needs more specific customization of the serialization for a specific type. This customization is possible via the ISerializerModifier.

Creating the modifier

To customize the serialization of a specific model type, one needs to implement the ISerializerModifier interface. The example belows shows how to encrypt the Password property on the Person model class.

Code Block
public class PersonSerializerModifier : SerializerModifierBase<Person>
{
    public override void SerializeMember(ISerializationContext context, MemberValue memberValue)
    {
        if (string.Equals(memberValue.Name, "Password"))
        {
            memberValue.Value = EncryptionHelper.Encrypt(memberValue.Value);
        }
    }
 
    public override void DeserializeMember(ISerializationContext context, MemberValue memberValue)
    {
        if (string.Equals(memberValue.Name, "Password"))
        {
            memberValue.Value = EncryptionHelper.Decrypt(memberValue.Value);
        }
    }
}

Registering the modifier

To register a modifier for a specific class, define the SerializerModifier attribute:

Code Block
[SerializerModifier(typeof(PersonSerializerModifier))]
public class Person : ModelBase
{
    // .. class contents
}
Note

Note that modifiers are inherited from base classes. When serializing, the modifiers defined on the most derived classes will be called last. When deserializing, the modifies defined on the most derived classes will be called first.

Serializing members using ToString / Parse

Sometimes types (classes or structs) don't implement a proper serialization mechanism. If they support proper ToString(IFormatProvider) and Parse(string, IFormatProvider) methods, there is no need to create a custom SerializerModifier to serialize these types. To let the serializers take care of this automatically, at least one of the following options must be true:

  1. The member is decorated using the SerializeUsingParseAndToString attribute
  2. The container class has a SerializerModifier that returns true in the ShouldSerializeMemberUsingParse method
Info

Note that decorating a member that does not implement proper ToString(IFormatProvider) and Parse(string, IFormatProvider) methods is useless, the serialization engine will ignore these types

For example, the class below is an excellent usage example of when to use this technique:

Code Block
    [Serializable, StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Vector
    {
        public double X;
        public double Y;
        public double Z;

        public Vector(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public string ToString(IFormatProvider formatProvider)
        {
            return $"{X.ToString(formatProvider)} {Y.ToString(formatProvider)} {Z.ToString(formatProvider)}";
        }

        public static Vector Parse(string value, IFormatProvider formatProvider)
        {
            var splitted = value.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
            var x = double.Parse(splitted[0], formatProvider);
            var y = double.Parse(splitted[1], formatProvider);
            var z = double.Parse(splitted[2], formatProvider);

            var vector = new Vector(x, y, z);
            return vector;
        }
    }

 

Customizing the serialization engines

Since the SerializerBase does all the heavy lifting, it is very easy to customize the behavior of an existing serializer or create a completely new one. Each serializer implements its own interface and are registered in the ServiceLocator using the following interfaces:

  • XmlSerializer => IXmlSerializer
  • BinarySerializer => IBinarySerializer

To customize a serializer, derive from an existing class and customize a method. The serializer below makes sure that specific members are never serialized. It keeps all other serialization logic intact.

Code Block
public class SafeXmlSerializer : XmlSerializer
{
    protected override bool ShouldIgnoreMember(ModelBase model, PropertyData property)
    {
        if (model is SecurityModel)
        {
            if (string.Equals(property.Name, "Password"))
            {
                return true;
            }
        }
        return base.ShouldIgnoreProperty(model, property);
    }
}

The only thing to do now is to register this custom instance in the ServiceLocator:

Code Block
ServiceLocator.Default

.

RegisterType<IXmlSerializer, SafeXmlSerializer>();

The following methods on the serializer classes might be of interest when customizing the serialization:

...

 

...