Generic/Dynamic XML file handling using Reflection and C#






3.33/5 (3 votes)
This article describes how to read XML files dynamically and as generic as possible using .NET Reflection.
Introduction
Consider an XML file that contains tags representing classes in your code, each of these classes having their own attributes. There is no specific order for these tags to be listed, and sometimes, some tags will not be listed within the XML file. How would you read such a file without the need to hard-code those attributes, or use switch
or if
statements?
This article will demonstrate how to do that without the need to hard-code anything using System.Reflection
methods.
Using the Code
Consider the following XML file, which represents the following class structure: a class Vehicle
that is a parent class for the child classes Aircraft
, Car
, Bus
, Motorcycle
, and Ship
representing different types of vehicles, each having its own attributes.
<Vehicle>
<Aircraft>
<Vehicle vehicleType="Presidential" Id="1"
Speed="800" Altitude="20000" NumberOfEngines="2" />
</Aircraft>
<Car>
<Vehicle vehicleType="None" Id="2" Speed="280" />
</Car>
<Car>
<Vehicle vehicleType="Private" Id="3" Speed="220" />
</Car>
<Aircraft>
<Vehicle vehicleType="Private" Id="4"
Speed="790" Altitude="30900" NumberOfEngines="4" />
</Aircraft>
<Bus>
<Vehicle vehicleType="Public" Id="5"
Speed="150" NumberOfPassengers="60" />
</Bus>
<Motorcycle>
<Vehicle vehicleType="Private" Id="6"
Speed="300" Racer="true" />
</Motorcycle>
<Motorcycle>
<Vehicle vehicleType="Government" Id="7"
Speed="180" Racer="false" />
</Motorcycle>
<Bus>
<Vehicle vehicleType="Government" Id="8"
Speed="190" NumberOfPassengers="48" />
</Bus>
<Ship>
<Vehicle vehicleType="None" Id="9"
Speed="50" Weight="10000" Steam="false" />
</Ship>
</Vehicle>
Now, we need to read this XML file and construct an object for each vehicle node and then save all of these objects in some data structure.
I used a dictionary of DataElement
s as follows:
// A dictionary that represents data read from the XML file
private readonly IDictionary<int, DataElement> vehicleDictionary =
new Dictionary<int, DataElement>();
where each DataElement
contains two properties: one for the VehicleType
and the other for the BaseVehicle
object, as follows:
public class DataElement
{
/// <summary>
/// The BaseVehicle object indicating whether this
/// element is a car, bus, aircraft, motorcycle or a ship.
/// </summary>
public BaseVehicle Vehicle
{
get;
set;
}
/// <summary>
/// The type of the vehicle indicated in VehicleType enumerator class.
/// </summary>
public VehicleType VehicleType
{
get;
set;
}
}
The VehicleType
is an Enum that represents types of vehicles such as presidential, private, public etc.
Now for the core of this article. I used an XmlDocument
object to access the XML file and carry out XML operations on it. To use that, we need to first load the XML file, and then read all tags included within the <Vehicle>
tag.
// Used to access the XML file and carry out XML operations
XmlDocument doc = new XmlDocument();
try
{
doc.Load("Vehicles.xml");
}
catch (Exception)
{
throw new Exception("File not found.");
}
// Holds all tags existing inside the main tag "Vehicle"
// i.e. holds all nodes in the XML file in their
// original XML format
XmlNodeList temp = doc.GetElementsByTagName("Vehicle").Item(0).ChildNodes;
After that, I used an Assembly
object (an object that contains the intermediate code, resources, and the metadata for a certain type of class, and is available under the System.Reflection
namespace) to maintain the assembly of BaseVehicle
.
// Used to hold all details of "GenericXML.Vehicle" including its child classes
Assembly asm = typeof(BaseVehicle).Assembly;
After that, I loop on every tag within the XmlDocument
object, performing the following:
- Getting the
Type
of the corresponding class for the current node name:
// Determines the type of the current node name
// by searching in the assembly object "asm"
Type tp = asm.GetType("GenericXML.Vehicle." + node.Name);
// The vehicle nodes inside the current "type" node
XmlNodeList itemsOfType = node.ChildNodes;
// An automatically initiated object of the current type
object obj = Activator.CreateInstance(tp);
DataElement tempElement = new DataElement
{
// Set the VehicleType by converting from string (in XML file)
//to corresponding Enum type
VehicleType = (VehicleType)Enum.Parse(typeof (VehicleType),
itemsNode.Attributes["vehicleType"].Value),
Vehicle = (BaseVehicle)obj
};
For setting the suitable attributes for each object created, I used Reflection again, but this time to access class attributes and have the accessibility to set them as well. To do that, I used the GetProperty().SetValue()
method.
// Set the value to the value mentioned in the XML file after conversion
// from string to suitable type
tp.GetProperty(att.Name).SetValue(tempElement.Vehicle, Convert.ChangeType
(att.Value, tp.GetProperty(att.Name).PropertyType), null);
Points of Interest
Now, the thing you should mind is the naming of the attributes in the XML file; names must match with, taking in mind, the case of letters. Either that, or just convert the attribute names to lower (or upper) case and compare.
Please feel free to inquire about anything that is not clear enough.