User Tools

Site Tools


thinkgeo_serialization_guide

ThinkGeo Serialization Guide

Note: The page was created before ThinkGeo 12.0. ThinkGeo 12.0 organized many classes into new namespaces and assemblies as well as had a few minor breaks in compatibility. The majority of previously built code should work without modification assuming the new namespaces are added. For guidance on upgrading your existing code, please check out ThinkGeo 12 Upgrade Guide.

Serialization & Deserialization

Using a File

For example, if we want to serialize a layer and save it to the hard disk, then we can use the GeoSerializer using the code below.

Layer layer = GetLayer();
GeoSerializer serializer = new GeoSerializer();
serializer.Serialize(layer, @"C:\layer.xml");

The layer can be recreated from the file like this:

Layer deserializedLayer = (Layer)serializer.Deserialize(@"C:\layer.xml", FileAccess.Read);

Using a Stream

Using streams opens up many possibilities such as streaming to and from isolated storage, using encryption, compression, etc. Internally we treat everything as stream and the overloads you see are simply to provide a friendly interface for you, the developer.

To a Stream:

using (MemoryStream memoryStream = new MemoryStream())
{
    serializer.Serialize(layer, memoryStream);
}

From a Stream:

using (FileStream fileStream = File.Create(@"C:\layer.xml"))
{
    Layer deserializedLayer = (Layer)serializer.Deserialize(fileStream);
}

Using a String

To a string:

string serializationResult = serializer.Serialize(layer);

Or from a string:

Layer deserializedLayer = (Layer)serializer.Deserialize(serializationResult);

Serialization Requirements

Attributes

The GeoSerializer honors the same attributes as the BinaryFormatter. These attributes are below.

SerializableAttribute

The GeoSerializer can only serialize and deserialize types that are decorated with SerializableAttribute, like this:

[Serializable]
public class Test
{
    //……
}

If the type “Test” has a field whose type is not decorated with the Serializable attribute, then that field should be decorated with NonSerializedAttribute, else the GeoSerializer will throw an exception when it tries to serialize that field's value.

OnSerializingAttribute

When applied to a method, specifies that the method is called during serialization of an object in an object graph. Example:

[OnSerializing]
private void SetValuesOnSerializing(StreamingContext context)
{
    // ……
}

OnSerializedAttribute

When applied to a method, specifies that the method is called after serialization of an object in an object graph. Example:

[OnSerialized]
private void SetValuesOnSerialized(StreamingContext context)
{
    // ……
}

OnDeserializingAttribute

When applied to a method, specifies that the method is called during deserialization of an object in an object graph. Example:

[OnDeserializing]
private void SetValuesOnDeserializing(StreamingContext context)
{
    // ……
}

OnDeserializedAttribute

When applied to a method, specifies that the method is called immediately after deserialization of an object in an object graph. Example:

[OnDeserialized]
private void SetValuesOnDeserialized(StreamingContext context)
{
    // ……
}

NonSerializableBaseTypeAttribute

The NonSerializableBaseTypeAttribute is useful when your type inherits from a type that is not serializable, for example:

[Serializable]
public class MyControl : Control
{
    private string textField;
 
    public MyControl()
    {
        textField = "test";
    }
}

The type “MyControl” inherits from System.Window.Control.Control. Now if we try to serialize an instance of MyControl, the serializer will throw an exception because the type “Control” contains fields of types that are not serializable. The NonSerializedAttribute we mentioned before does not help here, because we don't own the type “Control”, so we can't put attributes to its fields. At this point, our only solution is to put [NonSerializableBaseType] on “MyControl”:

[NonSerializableBaseType]
[Serializable]
public class MyControl : Control
{
    private string textField;
 
    public MyControl()
    {
        textField = "test";
    }
}

Now when we serialize an instance of “MyControl”, its base type's fields will be ignored, thus no exceptions.

GeoSerializer Structure

The GeoSerializer is a thin wrapper around GeoObjectModeler and GeoSerializationFormatter. The GeoObjectModeler converts the input object into the GeoObjectModel. The GeoObjectModel is then sent to GeoSerializationFormatter to be formatted into the appropriate form. In this way you can choose a format such as XML or JSon; you simply need an appropriate formatter. The GeoSerializer just gets the model from GeoObjectModeler and have the GeoSerializationFormatter to format the model.

GeoObjectModel

The GeoObjectModel is the intermediate form of the object before it is formatted. The GeoSerializationFormatter can save a GeoObjectModel to a stream, and it can also create a GeoObjectModel from a stream. These two types give us the flexibility of using different formats to represent the serialization result. When we serialize an object, we first create a GeoObjectModel based on the object, and then we use the GeoSerializationFormatter to save the model. When we deserialize, we first use the GeoSerializationFormatter to create a GeoObjectModel from the saved results, and then we recreate the object based on the model.

GeoObjectNode

GeoObjectNode is the basic unit that the GeoObjectModel is composed of. It contains information of an object that we need to serialize the object.

The main extension points are the protected virtual methods of GeoObjectModeler below.

GetMembers

This method gets members of a type. The members could be fields or properties. In most cases this offers a pivot point where you can override it, check for a certain member, in order to alter the model that will be built.

CreateMemberNode

In this method you will have an opportunity to convert the object passed in to the GeoObjectNode, which is a reflection of the object that gets sent to the formatter to be rendered into the final form. While you could wholly replace this method's code with your own system, its purpose is to allow you to control the flow of serialization. In most cases this offers a pivot point where you can override it, check for a certain member, modify it somehow and then call back into the base to do the heavy lifting. If you do choose to totally replace this method then be aware that its internal implementation calls itself recursively to walk the object tree.

CreateMemberObject

This method allows you the opportunity to convert the GeoObjectNode to the object it represents. While you could wholly replace this method's code with your own system, its purpose is to allow you to control the flow of deserialization. In most cases this offers a pivot point where you can override it, check for a certain member, modify it somehow and then call back into the base to do the heavy lifting.

Here is a sample of how to make your own serializer.

public class MyGeoObjectModeler : GeoObjectModeler
{
    protected override Collection<MemberInfo> GetMembers(Type type, GeoObjectModelerMemberTypes memberTypes)
    {
        //......
    }
 
    protected override void CreateMemberNode(string memberName, object memberValue, Type memberType, object 
 
memberOwner, GeoObjectNode memberNode)
    {
        //......
    }
 
    protected override object CreateMemberObject(string memberName, Type memberType, object memberOwner, 
 
GeoObjectNode memberNode)
    {
        //......
    }
}
 
public class MyGeoSerializer
{
    private MyGeoObjectModeler myGeoObjectModeler;
    private XmlGeoSerializationFormatter formatter;
    private BindingFlags bindingFlags;
 
    public MyGeoSerializer()
    {
        myGeoObjectModeler = new MyGeoObjectModeler();
        formatter = new XmlGeoSerializationFormatter();
        bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
    }
 
    public void Serialize(object serializationObject, string pathFileName)
    {
        GeoObjectModel model = myGeoObjectModeler.CreateModel(serializationObject, GeoObjectModelerMemberTypes.Fields, bindingFlags, GeoObjectModelerDefaultValueMode.ExcludeDefaultValues);
 
        using (FileStream fileStream = File.Create(pathFileName))
        {
            formatter.Save(model, fileStream);
        }
    }
}

Events

The GeoObjectModeler has four events that allow you to enhance the GeoObjectModeler without having to

inherit from it. They provide additional pivot points or ways to track the progress of buiding a model.

CreatingMemberNode

Raised before the value of a member is built into a GeoObjectNode.

MemberNodeCreated

Raised after the value of a member is built into a GeoObjectNode.

CreatingMemberObject

Raised before the value of a member is recreated.

MemberObjectCreated

Raised after the value of a member is recreated.

The names are pretty straightforward; users can write event handlers for these events to alter the serialization or deserialization results.

GeoSerializationFormatter

The GeoSerializer uses the XmlGeoSerializationFormatter by default, and it saves serialization results to XML documents. If you want the serialization results to be saved to other formats, you can implement your own GeoSerializationFormatter to do so. The GeoSerializationFormatter has two virtual methods:

SaveCore

Saves a GeoObjectModel to a stream.

LoadCore

Recreates a GeoObjectModel from a stream.

thinkgeo_serialization_guide.txt · Last modified: 2019/10/21 07:18 by tgwikiupdate