I am writing a program in C# which is use for save and load a list of object in specific structure like this:
SIGNATURE\r\n
SIZE\r\n
OBJECT_DATA\r\n
SIZE\r\n
OBJECT_DATA\r\n
\r\n
Signature
is a primitive string that contain a private key to separate my data file with others file.
SIZE
is an integer value present the number of bytes that OBJECT_DATA
use.
OBJECT_DATA
is a class that contain my data.
I've found many solution in Stackoverflow and almost all of them use BinaryFormatter
which is insecure and should not be use.
I also took a look at protobuf-net but I still confused to apply it for my problem.
I think the serialize and deserialize stage for the solution will look like this:
Serialize (assume that the input/output file is a binary file):
1. Open output file and write the signature.
2. Loop over the source object list
- With each item, calculate the size of item and write it to output file.
- Write object data to binary file. (I'm still confusing in this step)
- Write "\r\n" to mark this is the end of the item.
Deserialize
1. Open input file and read the Signature
2. If the Signature fit with my secret key:
- Loop until end of file
- Read the SIZE value.
- Read object and create instance of it's class.
The OBJECT_DATA
I mentioned is an instance of a class:
public abstract class IShape
{
public State Configuration { get; set; }
public abstract string Name { get; }
public List<Point> Points { get; set; } = [];
public abstract UIElement Draw();
public abstract IShape Clone();
public BitmapImage Preview { get; set; }
}
The structure of State Configuration
:
public class State
{
public DoubleCollection StrokeDashArray { get; set; }
public SolidColorBrush Stroke { get; set; }
public SolidColorBrush Fill { get; set; }
public double StrokeThickness { get; set; }
}
I think Protobuf-net is a good way to serialize an object to Stream
but my class contains some value from System.Windows.Media
and I'm not sure if I can serialize it with using Protobuf-net.
I solve my own problem by combining IShape
and State
into a POCO class.
public class ShapeProtocol
{
public string Name { get; set; }
public string StartX { get; set; }
public string StartY { get; set; }
public string EndX { get; set; }
public string EndY { get; set; }
public string StrokeThickness { get; set; }
public string Stroke { get; set; }
public string? StrokeDashArray { get; set; }
public string? Fill { get; set; }
public ShapeProtocol() { }
public ShapeProtocol(IShape shape)
{ /* Constructor */ }
public static IShape ConvertBack(ShapeProtocol protocol)
{ /*...*/ }
}
Then I implement a ShapeSerializer
:
public static class ShapeSerializer
{
public static string Serialize(IShape shape)
{
var shapeProtocol = new ShapeProtocol(shape);
var properties = from p in shapeProtocol.GetType().GetProperties()
where p.GetValue(shapeProtocol, null) != null
select p.Name + "=" + p.GetValue(shapeProtocol, null);
// this string will be the OBJECT_DATA I mentioned.
// the result will look like this: Name=Ellipse;StartX=240;StartY=48.2;EndX=411.3;EndY=140;StrokeThickness=1;Stroke=#FFDC143C
return String.Join(";", properties.ToArray());
}
public static IShape Deserialize(string rawData)
{
var _raw = rawData.Split(';').ToDictionary(p => p.Split("=")[0], p => p.Split("=")[1]);
var deserialized = new ShapeProtocol();
var _properties = typeof(ShapeProtocol).GetProperties();
foreach (var property in _properties)
{
try
{
var value = _raw[property.Name];
property.SetValue(deserialized, value, null);
}
catch { /* assume that value is null */ }
}
return ShapeProtocol.ConvertBack(deserialized);
}
}
Then I just use FileStream
to store/load the data with the file structure in the very fist of this problem.
Expressing my gratitude to @JonSkeet and @MarcGravell. Both of you have been a great source of inspiration in addressing this issue.