Introduction
Many articles on Code Project discuss how to save application settings. My solution is an attempt to make the saving non-destructive, transparent and traceable. It is based on merging XML files that contain the serialized class instances identified by GUIDs.
Sample application
In the sample application, I will show how to implement an employee database application base. In the first delivered version the application should manage these employee details:
- Name: String
- Salary: Double
- Birthday: DateTime
- Information if the employee is a US citizen: Boolean
I simulate a customer who is outside of the US. The searcher doesn't need the citizenship information, but wants to know if the employee is male. So, they want to edit these fields:
- Name: String
- Salary: Double
- Birthday: DateTime
- Male: Boolean
The third configuration is an evolution of the application in which the application developer introduces a new field: married.
- Name: String
- Salary: Double
- Birthday: DateTime
- Information if the employee is an US citizen: Boolean
- Married: Boolean
If the customer deploys the new version of application, it should see these fields:
- Name: String
- Salary: Double
- Birthday: DateTime
- Male: Boolean
- Married: Boolean
In this very simple scenario, it is clear that there are two parties which concurrently change the behavior of the application through configuration.
Data model
In the data model I have to describe the UI. I can have several delivered fields, which the end user can change and eventually also add to. I decided to describe the fields with the following properties: Name: string
, Visible: bool
and DataType: string
. I will need a persistent container for such descriptions.
public class FieldCollection: ElementList<Field> //Container of Fields
{
}
public class Field : OwnedElement<FieldCollection>
//Field belongs to FieldCollection
{
public const string XMLDataType = "type";
public const string XMLVisible = "visible";
private string _dataType;
// DataType property will be serialized as type attribute
[Serialization(XMLDataType,
SerializationSettings.SerializeAsAttribute)]
public string DataType
{
get
{
return _dataType;
}
set
{
if (_dataType != value)
{
// here we track the changes into local change list
LogChange(XMLDataType, _dataType, value);
_dataType = value;
}
}
}
private bool _visible = true;
[Serialization(XMLVisible)]
//if the engine saves the complete state of class it
// doesn't write the default values
[DefaultValue(true)]
public bool Visible ...
}
The missing declaration of the Name
property is already in the ancestor of OwnedElement
. The persistent form of the first configuration looks like this:
<e:FieldCollection xmlns:e="elements">
<e:Field type="System.String" ID="36a63bf3-1654-4818-a173-7845048d8e66"
Name="Name" />
<e:Field type="System.Double" ID="06f9d30b-3d34-4769-aaa7-f4abbf8f1df1"
Name="Salary" />
<e:Field type="System.DateTime" ID="93a16832-5f25-43d4-9a0b-e6a6d16866c7"
Name="Birthday" />
<e:Field type="System.Boolean" ID="1d46058c-c17b-41b5-b0b0-ec7ae55021fc"
Name="US_Citizen" />
</e:FieldCollection>
In this small XML, I serialized a collection of 4 Fields of specified name and type. All Fields have visible = true
which is the default value and therefore not serialized. In the second configuration, the user adds the new Male field and hides the field US_Citizen. These changes on configuration are serialized into XML. In namespace o
are the old saved values.
<e:FieldCollection xmlns:e="elements" xmlns:o="versioning">
<e:Field visible="0" o:visible="1"
ID="1d46058c-c17b-41b5-b0b0-ec7ae55021fc" />
<e:Field type="System.Boolean" ID="f3ab16b0-f33f-4919-9c5c-29419d7ada7e"
Name="Male" />
</e:FieldCollection>
If the application now reads the first XML file and then the second file, the engine finds in the second file the same ID (GUID). It identifies the Field and then updates its properties. The engine also adds a new Field to the collection.
The code
Generating the first configuration:
FieldCollection fieldCollection = new FieldCollection();
Field nameField = fieldCollection.Add("Name");
nameField.DataType = typeof(string).FullName;
Field salaryField = fieldCollection.Add("Salary");
salaryField.DataType = typeof(double).FullName;
Field birthdayField = fieldCollection.Add("Birthday");
birthdayField.DataType = typeof(DateTime).FullName;
Field usField= fieldCollection.Add("US_Citizen");
usField.DataType = typeof(Boolean).FullName;
Writer.Write(fieldCollection, "ver1.xml", false);
The generation is pretty simple. I need to instantiate a new FieldCollection
and insert Fields
into it. Then I can write with Writer
the erialized form of the configuration into an XML file. Installation changes are:
FieldCollection fieldCollection = new FieldCollection();
Reader.Read("ver1.xml", fieldCollection, null);
fieldCollection.AcceptChanges();
Field usField = fieldCollection.Find("US_Citizen");
if (usField != null)
usField.Visible = false;
Field maleField = fieldCollection.Add("Male");
maleField.DataType = typeof(Boolean).FullName;
Writer.Write(fieldCollection, "installation.xml", true);
It's again very simple. First, I read the fieldCollection
with its Field
s. Then I make one field invisible and add a new field. Finally, I save the changes. Configuration merging:
public static FieldCollection Merge(params string[] fileNames)
{
FieldCollection result = new FieldCollection();
foreach (string fileName in fileNames)
{
Reader.Read(fileName, result, null);
}
return result;
}
use it as:
FieldCollection result = Merge("ver1.xml", "instalation.xml");
Merging of configurations is pretty simple. First, I need to read the base configuration and then apply the change log to it.
What next?
In the next article, I will describe the library in detail.
History
- 22 May, 2007 - Original version posted