Usually a model which represents a configuration or application data is serialized in order to reuse it. Concept provides a powerful mechanism to save and load data from XML file without writing code. This mechanism also works with polymorphous elements and links between elements.
To illustrate the powerful serialization mechanism of Concept, we will use the machine model described by the UML diagram below. The goal is to show how to easily save and load an XML file.
The load and unload methods of FrameworkController are called by the Boostrapper on application startup and application shutdown (See Application root chapter in introduction to application business model). We use these methods to load and save the machine configuration. All ConceptComponent provide the LoadFromFile and SaveToFile methods which are used to save and load any kind of model easily. If an error occurs during the deserialization the MessageCollection will give the details about what went wrong. In the following example, we show the deserialization errors in a message box using a ConceptMessage.
public sealed class FrameworkController { public Machine Machine { get; private set; } public void Load() { Machine = new Machine() { Description = "My Machine Description" }; LoadConfiguration(); } public void Unload() { SaveConfiguration(); } public void LoadConfiguration() { MessageCollection messages = new MessageCollection(); Machine.LoadFromFile("MyMachineConf.cxml", PluginClassManager.AllFactories, messages); if (messages.Count > 0) ConceptMessage.ShowError(string.Format("Error while loading the configuration file:\n{0}", messages.Text), "Loading Error"); } public void SaveConfiguration() { Machine.SaveToFile("MyMachineConf.cxml"); } }
In order to serialize a class, it is necessary to specify which ones of its properties need to be serialized. To accomplish that, Concept provides a C# attribute called: ConceptSerialized. This attribute is only available on properties of value types. As an example, see the Description property of Machine class.
Concept gives the possibility to specify which object inside the internal list of a ConceptContainer should not be serialized. By default, all the components within the internal list of a ConceptContainer are serialized whether they are created with the help of the ConceptAutoCreate attribute or manually (see Creating a new ConceptComponent chapter in designing and implementing a configuration model). To disable serialization of an object of the internal list, set the Options property with the ConceptOptions.NotSerialized value as illustrated with the Y axis in the machine constructor below.
[IntlConceptName("Framework.Machine.Name", "MyMachine")] [ConceptSmallImage(typeof(Machine), "/Images/Machine32x32.png")] [ConceptLargeImage(typeof(Machine), "/Images/Machine64x64.png")] public class Machine : ConceptComponent { public Machine() { AxisY = AddNew<Axis>(); AxisY.Options |= ConceptOptions.NotSerialized; } [ConceptSerialized] [IntlConceptName("Framework.Machine.Description", "Description")] public string Description { get { return _description; } set { if (_description != value) { _description = value; DoPropertyChanged(DescriptionPropertyName); } } } private string _description; public const string DescriptionPropertyName = "Description"; [ConceptAutoCreate] [IntlConceptName("Framework.Machine.AxisX", "Axis X")] public Axis AxisX { get; protected set; } [IntlConceptName("Framework.Machine.AxisY", "Axis Y")] public Axis AxisY { get; protected set; } }
[IntlConceptName("Framework.Axis.Name", "Axis")] [ConceptSmallImage(typeof(Axis), "/Images/Axis32x32.png")] [ConceptLargeImage(typeof(Axis), "/Images/Axis64x64.png")] public class Axis : ConceptComponent { [ConceptSerialized] [IntlConceptName("Framework.Axis.SpeedMaximum", "Maximum speed")] public double SpeedMaximum { get { return _speedMaximum; } set { if (_speedMaximum != value) { _speedMaximum = value; DoPropertyChanged(SpeedMaximumPropertyName); } } } private double _speedMaximum; public const string SpeedMaximumPropertyName = "SpeedMaximum"; }
The serialization mechanism provided by Concept save the data model in a XML file. Below the configuration file of this simple model.
<MyMachine> <Description>My Machine Description</Description> <AxisX> <SpeedMaximum>5</SpeedMaximum> </AxisX> </MyMachine>
By default, Concept serialization supports the following types:
Concept allows developers to easily extend the serialization supported types. The first step to add new types is to create your own converter and make it inherit from the ConceptStringConverter. The example below is a converter for the point class. This converter must be able to save the object to a string and reconstruct the object from the saved string.
public class PointConceptConverter : ConceptStringConverter { public override object ConvertFrom(string value) { return Point.Parse(value); } public override string ConvertTo(object value) { Point point = (Point)value; return point.ToString(CultureInfo.InvariantCulture); } public override Type Type => typeof(Point); }
When the Converter is written, register it inside Concept. Usually this action is done in the load method of the framework controller (See Application root chapter in introduction to application business model).
public void Load() { ConceptUtilsUtils.AddStringConverter(new PointConceptConverter()); }
In some cases, the use of converters is not enough. Therefore, you have to modify the default Concept serialization by overriding the WriteTo and the ReadFrom methods of the ConceptElement. The IDataStoreNode parameter of these methods provides tools to write and read different types from a XML file. The example below overrides the Concept serialization to integrate the serialization of the point class.
public class Machine : ConceptComponent { public override void WriteTo(IDataStoreNode node) { base.WriteTo(node); node.WriteString(HomePointPropertyName, HomePoint.ToString(CultureInfo.InvariantCulture)); } public override void ReadFrom(IDataStoreNode node, ConceptFactories factories, IMessageCollector messageCollector) { base.ReadFrom(node, factories, messageCollector); string val = node.ReadString(HomePointPropertyName, default(Point).ToString(CultureInfo.InvariantCulture), messageCollector); HomePoint = Point.Parse(val); } public Point HomePoint { get; set; } public const string HomePointPropertyName = "HomePoint"; }