Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Customize an application with XML fragments

1.44/5 (3 votes)
22 May 20073 min read 1   124  
How to customize an application using XML fragments

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:

  1. Name: String
  2. Salary: Double
  3. Birthday: DateTime
  4. 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:

  1. Name: String
  2. Salary: Double
  3. Birthday: DateTime
  4. Male: Boolean

The third configuration is an evolution of the application in which the application developer introduces a new field: married.

  1. Name: String
  2. Salary: Double
  3. Birthday: DateTime
  4. Information if the employee is an US citizen: Boolean
  5. Married: Boolean

If the customer deploys the new version of application, it should see these fields:

  1. Name: String
  2. Salary: Double
  3. Birthday: DateTime
  4. Male: Boolean
  5. 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.

XML
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:

XML
<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.

XML
<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:

C#
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;

// write the fieldCollection into ver1.xml
// last parameter means write the not the changes on 
// fieldCollection but the whole state
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:

C#
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;

// last parameter = true means save changes
Writer.Write(fieldCollection, "installation.xml", true);

It's again very simple. First, I read the fieldCollection with its Fields. Then I make one field invisible and add a new field. Finally, I save the changes. Configuration merging:

C#
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here