Click here to Skip to main content
15,886,578 members
Articles / Programming Languages / C# 5.0
Tip/Trick

Automated Serializer for WinRT Platform

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
19 Aug 2014CPOL4 min read 10.3K   3   2
How to serialize an object without having to manually instruct the DataContractSerializer class.

Introduction

Recently I started to have fun with Windows Store Application and Window Phone Application, so I created a multiplayer version of the Tic Tac Toe game with Universal Application, where a player can host with his Tablet or Phone and the other players can scan the (local) network to find and connect to him. To make this application I want to use my personal library wich contains a lot of ready-to-use code for almost everything, but I get some compatibility issue. In particular I've noticed that in the WinRT platform the BinaryFormatter class has been removed, forcing us to fall back on the DataContractSerializer. Nothing wrong with this except that while the BinaryFormatter class was able to automatically serialize an object and all its graph and deserialize it without problems, the DataContractSerializer needs to know the Type you want to serialize and every types that can occour in the object graph and this is very tedious when you use a lot of serialization for data transmission.

You can deal with this by manually adding all the Types you have to serialize in a collection and pass it in the DataContractSerializer constructor with the knowTypes arg. The drawback is that you have to add or remove the Type in the collection every time you set or unset a class with the [DataContract] attribute. Also you have to previously know the types you are about to serialize and this is not always possibile. You can make use of generics, but this can be problematic when you have to serialize complex class. For instance my GameServer class become

public class GameServer<G, M, P> where G : Game where M : Move where P : Player
{
    ...
    G g = Serializer.Deserialize<G>();
    ...
}

Neither using Interfaces can save you, as you can't deserialize a Person object to it's generic IPerson interface!

IGame g = Serializer.Deserialize<IGame>(); // error!

The type must be the direct type that you want to deserialize, and not a base one.

Purpose of the Tips

All I want is to get a class that looks like the old Serializer build upon the BinaryFormatter class, that is either static and "two methods" only, using the DataContractSerializer class. I want to achieve this because:

  1. I like the "two method" static Serializer class
  2. I have a lot of code that can actually work with WinRT but fails because it relies on my old Serializer Class

So all I need is a class that looks like this to the consumer's eyes:

C#
public static class Serializer
{
    public static byte[] Serialize(object obj)
    {
        ...
    }

    public static object Deserialize(byte[] buffer)
    {
        ...
    }
}

 

Using the code

With that class I can do this in a dotNet platform:

C#
My Library
    class IMove
    {
    }

    class GameServer
    {
        void SendMove(IMove move)
        {
            byte[] buffer = Serializer.Serialize(move);
            Send(buffer);
        }

        IMove RecvMove()
        {
            byte[] buffer = Recv();
            return Serializer.Deserialize(buffer) as IMove;
        }
    }

Tic Tac Toe Project
    class TrisMove : IMove
    {
    }

    GameWindow.cs
        SendTurn(...)
        {
            var move = new TrisMove(){...};
            server.SendMove(move);
        }

As you can see I can deserialize an object without specify any type and then use an interface to handle it.

 

Points of Interest

To get a Serializer that can automatically handle all Types that are marked with the DataContract Attribute avoiding the need to manually add every class to the DataContractSerialize, I've made use of Reflection to load all the assemblies inside the installation folder of my app, and extract and store all the types marked with the DataContract attribute. I have achieved this with the following code:

C#
static readonly string[] SUPPORTED = new[] { ".dll", ".exe" };
static readonly object LOCKER = new object();
static bool initialized = false;
static Dictionary<string, Type> knowTypes;

static async Task Init()
{
    // Get all Types marked with DataContract attribute in this application
    // regardless of the assembly their are saved
    knowTypes = (await Package.Current.InstalledLocation.GetFilesAsync())
                    .Where(f => SUPPORTED.Contains(f.FileType.ToLower()))
                    .SelectMany(f => Assembly.Load(new AssemblyName(f.DisplayName))
                                    .DefinedTypes
                                    .Where(t => t.GetCustomAttributes(typeof(DataContractAttribute), true).Any()))
                    .ToDictionary(kv => kv.FullName, kv => kv.AsType());
    // Mark the class as Initialiazed
    initialized = true;
}

I made use of Linq to compact the code but is fairly simple to understand:

  1. Get all files in the installation folder
  2. Get only the assemblies file
  3. For each file load the assembly, extract all the types marked with DataContract Attribute and project them into a single collection
  4. Convert that collection to a Dictionary where the Key is the FullName of the Type and the Value is the Type itself

My methods becomes:

C#
public static byte[] Serialize(object obj)
{
    lock (LOCKER)
    {
        if (!initialized)
            Init().Wait();
    }

    using (var ms = new MemoryStream())
    {
        // Write the FullName of the Type in the stream
        var bw = new BinaryWriter(ms);
        bw.Write(obj.GetType().FullName);

        // Serialize the object in the stream
        DataContractSerializer serializer = new DataContractSerializer(obj.GetType(), knowTypes.Values);
        serializer.WriteObject(ms, obj);
        ms.Flush();

        // return as Byte Array
        return ms.ToArray();
    }
}

public static object Deserialize(byte[] buffer)
{
    lock (LOCKER)
    {
        if (!initialized)
            Init().Wait();
    }

    using (var ms = new MemoryStream(buffer))
    {
        // Read the FullName from the string
        var br = new BinaryReader(ms);
        string name = br.ReadString();

        // Get the right Type
        var type = knowTypes[name];

        // Automatically deserialize using the right type
        DataContractSerializer serializer = new DataContractSerializer(type, knowTypes.Values);

        // return the correctly deserialized object
        return serializer.ReadObject(ms);
    }
}

With this code the new Serializer build upon the DataContractSerializer looks exacty as my old Serializer, and this make me very happy! Now I can serialize and deserialize my object and send it throught my tablet and my phone, and more important I can re-use a lot of code inside my personal library that rely on the "old" Serializer Class!

About

First of all this is my very first article so I'm sure there is a lot of error and mistake. I hope I can count on all of you to identify and correct any spelling, syntax and logic errors that i may have entered into this little Tips. I know there are other posts about my own topic but none of them makes use of reflection to load all types involved in the serialization process, so I hope I have brought a small contribution to a community that has given me so much over the years.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionmy vote of 5, but..... Pin
gmolition1-Apr-15 5:30
gmolition1-Apr-15 5:30 
AnswerRe: my vote of 5, but..... Pin
Alberto Nuti11-Aug-15 9:33
Alberto Nuti11-Aug-15 9:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.