Wednesday, 17 May 2017

Serialization 02 ISerializable

Now binary serialization doesn't exactly produce human readable results but by no means are they secure. However you can implement the ISerializable interface to provide custom binary serialization.

[Serializable]
class Person : ISerializable
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string FirstName, string LastName) {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
    protected Person(SerializationInfo info, StreamingContext ctx) {
        FirstName = info.GetString(nameof(FirstName));
        LastName = info.GetString(nameof(LastName));
    }

    public string FullName { get { return $"{FirstName} {LastName}"; } }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext ctx) {
        info.AddValue(nameof(FirstName), FirstName);
        info.AddValue(nameof(LastName), LastName);
    }
}

[Serializable]
class Employee : Person
{
    static int _runningId;
    public int Id { get; private set; } = _runningId++;
    public Employee(string FirstName, string LastName) : base(FirstName, LastName) { }

    protected Employee(SerializationInfo info, StreamingContext ctx) : base(info, ctx) {
        Id = info.GetInt32(nameof(Id));
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext ctx) {
        info.AddValue(nameof(Id), Id);
        base.GetObjectData(info, ctx);
    }

}

now the complexity of implementing the ISerializable interface does increase the complexity of the serializable classes, but upon inspection it's rather simple.

  • The interface enforces the implementation of the GetObjectData method, this method fires on serialization, which gives you the opportunity to encrypt your data. For simplicity sake we're using the SercurityPermission attribute, but you could implement whatever mechanism you please.
  • Then we add a constructor that is leveraged by the BinaryFormater to rebuild our objects.
  • Because we use inheritance we only implement ISerializable on our base class
    • We override the GetObjectData method in our superclasses and call the base implementation of it
    • We call the base deserializable constructor from our superclasses.
Now our use of the BinaryFormater doesn't really change all that much... or at all


class Program
{
    static void Main(string[] args)
    {
        var ppl = new Person[] {
        new Person("John", "Smith"), new Person("Jane","Doe"),
        new Employee("Sally","Johnson"),new Person("Alejandro", "Cruz"),
        new Person("Diago","Pendaz"), new Employee("Tim","Chan")};

        var formatter = new BinaryFormatter();

        using (var fs = new FileStream(@"c:\people.data", FileMode.Create, FileAccess.Write))
            formatter.Serialize(fs, ppl);

        Person[] People;
        using (var fs = new FileStream(@"c:\people.data", FileMode.Open, FileAccess.Read))
            People = formatter.Deserialize(fs) as Person[];

        foreach (var p in People)
            Console.WriteLine(p.FullName);
    }
}


Implementing the ISerailizable interface is no trivial matter here's a list of caveats to look out for.