Introduction
The use of XML for the right purpose and at the right place can help developers very much. WPF (Windows Presentation Foundation), WWF (Windows Workflow Foundation), and WCF (Windows Communication Foundation) are some of the technologies which make use of it for the right purpose and at the right place.
When I first started playing with my LEGO set when I was about 5 years old, I was plugging pieces from the same kit or even from others, to create new toys and to play with them. It was a good decision by my father not to buy me a new toy every time I needed one, but instead give me a set with which I could create my toys, thus decreasing the costs :)
After I met XML, I said to myself: "Let's connect the pieces together and create new toys". One of these toys was the "Friendship of XML and Reflection". It is a toy with two main pieces: an XmlLayer
, and an Element
where XmlLayer
is the base on which you build your toys and the Element
is the base from which you create new pieces or toys.
Deliverables
I have included the source code for the library along with a demo application. I will add the documentation and the Unit Test code as soon as I find the time and energy (I am currently working on four projects at the same time).
Design
The major entities of this design are:
Element
- A custom XmlElement
base class which provides Dictionary
, List
, and ValueConversion
support.XmlLayer
- A custom XmlDocument
class which provides Reflection and element indexing support (without DTD).Parameter
- A strongly typed Element
class which is designed to store information that can be called as a Parameter
(e.g., algorithm parameters, connection string parameters).Property
- A strongly typed Element
class which is designed to store information for an entity. For me, an entity is something which can be defined with properties. These properties can be hardcoded like a Student
entity having name, age, and class properties, but each of them is stored as a single property in the code accompanying this article.
XmlLayer
The XmlLayer
class provides Reflection support to create strongly-typed Element
(s).
public class XmlLayer : XmlDocument
{
public void RegisterType(string localNameOrAlias, Type type);
public override XmlElement CreateElement(string prefix,
string localName, string namespaceURI);
public override XmlElement GetElementById(string elementId);
private void ReadElements(XmlReader reader);
protected virtual Element OnDefaultElementNeeded(string prefix,
string localName, string namespaceURI);
}
RegisterType
registers an Element
having the specified local name or alias with the XmlLayer
. Let's say you have an XML file containing elements prm
, top
, and connection_string
. You can create your custom Element
s such as Parameter
, Topic
, and ConnectionString
, each inheriting from the Element
class, and register them with the aliases prm
, top
, and connection_string
. When the XmlLayer
is loaded from this XML file, it will automatically create your custom wlements instead of simple XmlElement
(s), when it hits these aliases.CreateElement
overrides the XmlElement.CreateElement(....)
base method and creates custom Element
(s) instead of the default XmlElement
. It checks whether the local name was registered with a custom Element
type. If so, it then creates the instance of that type through Reflection.GetElementById
overrides the XmlElement.GetElementById(....)
base method. It looks up the elements from an internal HashTable
instance containing WeakReference
(s) to created elements. This removes the necessity of using DTDs for element lookup.ReadElements
reads only the registered elements from a XmlReader
. It skips the non-registered ones, preserving the element hierarchy and attributes. It is basically there to filter out unwanted elements which should not be part of our custom Element
(s). It is called from the LoadRegisteredTypes
method which reads an XML stream/file sequentially without reading the whole stream/file at once into memory. You can parse a huge XML file, extracting only what you need.OnDefaultElementNeeded
is called from the overridden CreateElement
method to create custom base element types in case a non-registered element is to be created. You can override this method in your own XmlLayer
class to create base/default elements of type MyBaseElement
, for example.
Element
The Element
class provides Dictionary
, List
, and ValueConversion
support for strongly-typed Element
(s).
public class Element: XmlElement, ILayerList, ILayerDictionary, IUnique, IEnumerable
{
public override string InnerText{ get; set; }
public new string Value { get; set; }
protected virtual void SetValueCore(string newValue)
public void SetValue(System.Int32 value)
public void SetValue(System.Double value)
public void SetValue(System.DateTime value)
public void SetValue(System.Enum value)
.
.
.
public System.Int32 ValueAs(System.Int32 defaultValue)
public System.Double ValueAs(System.Double defaultValue)
public System.DateTime ValueAs(System.DateTime defaultValue)
public TEnum ValueAsEnum<TEnum>(TEnum defaultValue)
.
.
.
protected Element GetChildElement(string elementOrPath, bool createIfNotExists)
protected virtual IEnumerable<TLayerElement> OfType<TLayerElement>()
where TLayerElement : Element
}
InnerText
overrides the XmlElement.InnerText
base property. It calls the SetValueCore
virtual method. The idea behind is to give custom elements the ability to detect value changes and fire events. The Element
class also hides the Value
property, and calls the InnerText
property's get
and set
accessors. All these centralize the value change control to one method: SetValueCore
.
GetChildElement
gets an element from a backslash separated path of element names (e.g., \Books\Book\Author). The element returned is the last element in the path, which is Author. This method optionally creates the element if it does not exist.OfType<TLayerElement>()
filters the child elements based on a specified type.
Property
The Property
class represents a single property of an entity. It is contained in a Properties
element which is a collection of properties.
public class Property: Element, System.ComponentModel.INotifyPropertyChanged
{
protected override void SetValueCore(string newValue)
private void NotifyPropertyChanged(string property)
}
SetValueCore
overrides the base class method to notify subscribers of the changes to the value of the property.NotifyPropertyChanged
informs the subscribers of the System.ComponentModel.INotifyPropertyChanged.PropertyChanged
event. It also calls the OnPropertyChanged
method of the Properties
collection which owns this property.
Properties
The Properties
class represents a collection of properties. When an entity such as class Student
: Element
is created, the Properties
collection can be appended to it to provide easy property creation and clean persistence.
public class Properties : Element, ILayerList<Property>>
{
public event EventHandler<PropertyChangedEventArgs> PropertyChanged
public new Property this[string name] { get; set; }
public bool HasProperty(string name)
protected virtual Property GetProperty(string name)
protected internal virtual void OnPropertyChanged(string property)
}
PropertyChanged
is the event which is fired when the value of a property in the collection changes.
this[string name]
gets the property with the specified name. By default, creates the property if it does not exist, providing dynamic property support at runtime.
HasProperty
checks whether a property with the specified name exists in the collection.OnPropertyChanged
is called from a property contained in the collection when its value changes.
Parameter
The Parameter
class represents a single parameter which stores a numerical or textual value. It is basically a value consumed in a measurement or calculation.
[TypeConverter(typeof(ParameterConverter))]
public class Parameter : Element, System.ComponentModel.ICustomTypeDescriptor
{
public string Description { get; set; }
public string Alias{ get; set; }
protected virtual PropertyDescriptorCollection FilterProperties(
PropertyDescriptorCollection pdc)
}
Description
is the custom information for a parameter which provides additional information besides the name of the parameter.Alias
is the custom name for a parameter. Let's say, you receive an algorithm specification where you have an input parameter named TFTMin
and you want to display a more meaningful name to the user via the GUI to modify this parameter. This is exactly the reason to have an alias for a parameter.
FilterProperties
filters the properties to be displayed when a parameter is attached to a .NET PropertyDescriptor
control, which I use for its simplicity.
Parameters
The Parameters
class represents a collection of parameters and sub-parameter collections.
[TypeConverter(typeof(ParameterCollectionConverter))]
public class Parameters : Element, ILayerList<parameter>,
System.ComponentModel.ICustomTypeDescriptor
{
public new Parameter this[string name] { get; }
public virtual Parameter CreateParameter(string name))
public Parameters GetSubset(string name)
public TParameters GetSubset<tparameters>() where TParameters : Parameters
protected virtual PropertyDescriptorCollection
FilterProperties(PropertyDescriptorCollection pdc)
}
this[string name]
gets the parameter with the specified name.
CreateParameter
creates a parameter with the specified name.GetSubset
gets the sub-parameters collection with the specified name.GetSubset<TParameters>
gets the strongly-typed sub-parameters collection with the specified name.FilterProperties
filters the properties to be displayed when a parameter collection is attached to a .NET PropertyDescriptor
control, which I use for its simplicity.
I am a software developer who likes to code his ideas in his freetime. I like playing strategy games and going to spearfishing.