Overview
This article continue the series of Reflecting Data to .NET Classes. In the first article, we discussed the concept of Reflecting Data. The techniques behind it were also explained and demonstrated. The source of data demonstrated in this article will be XML documents. This is kind of different from the HTML Forms as in the first article. However, the concept and the techniques remain the same. In this article, I will demonstrate how you can apply the same technique in reflecting data to .NET classes from XML documents.
Description
This article assumes you have some basic understand of Reflection. You can refer back to the first article for more information about it at:
http://www.c-sharpcorner.com/UploadFile/tinlam/ReflectionInNetTLP111232005061511AM/ReflectionInNetTLP1.aspx
The source code for the example in this article was developed with the VS.NET, and .NET Framework SDK (RTM). The example consists of 4 files.
(1) The employee.cs; it contains the information about an employee.
(2) The FormXML.cs; it contains 2 static methods to help us fill the properties.
(3) The XML document named ofthemonth.xml; it contains the data to fill to the properties.
(4) The test.cs; its a class that will be compiled to be an executable. It will show us that the class DataReflector.XMLDocument does its work correctly.
First, lets see how the employee.cs looks like:
using System;
namespace BusinessEntity
{
public class employee
{
private int _employeeID = -1;
private string _firstName = "";
private string _lastName = "";
private string _emailAddress = "";
private string _phoneNumber = "";
private int _departmentID = -1;
private int _bunchLocationID = -1;
private bool _isManager = false;
private DateTime _hiredDate;
public int employeeID
{
set { _employeeID = value; }
}
public string firstName
{
set { _firstName = value; }
}
public string lastName
{
set { _lastName = value; }
}
public string emailAddress
{
set { _emailAddress = value; }
}
public string phoneNumber
{
set { _phoneNumber = value; }
}
public int departmentID
{
set { _departmentID = value; }
}
public int bunchLocationID
{
set { _bunchLocationID = value; }
}
public bool isManager
{
set { _isManager = value; }
}
public DateTime hiredDate
{
set { _hiredDate = value; }
}
public employee() {}
public new string ToString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append( "\nEmployee ID : " );
sb.Append( _employeeID );
sb.Append( "\nFirst Name : " );
sb.Append( _firstName );
sb.Append( "\nLast Name : " );
sb.Append( _lastName );
sb.Append( "\nEmail Address : " );
sb.Append( _emailAddress );
sb.Append( "\nPhone Number : " );
sb.Append( _phoneNumber );
sb.Append( "\nDepartment ID : " );
sb.Append( _departmentID );
sb.Append( "\nBunch Location ID : " );
sb.Append( _bunchLocationID );
sb.Append( "\nIs Manager : " );
sb.Append( _isManager );
sb.Append( "\nHired Date : " );
sb.Append( _hiredDate );
return sb.ToString();
}
}
}
Nothing is too interesting here. Just a few member fields, properties and a ToString() method which displays the data hold in the private member fields.
Now, How about the FormXML.cs?
using System;
using System.Xml;
using System.Reflection;
namespace DataReflector
{
/// <summary>
/// The class that contains methods to map xml data to class properties
// </summary>
public class XMLDocument
{
/// <summary>
/// This method accepts a string containing the path to the XML file. And an object
/// whose properties are to be filled by the data from the xml file.
/// </summary>
/// <param name="obj">the object, whose properties are to be filled</param>
/// <param name="filepath">the path to the XML file</param>
/// <returns>true if no error occurs, false otherwise</returns>
public static bool fillProperties(object obj, string filepath)
{
Type t = null;
PropertyInfo[] properties = null;
object currentValue = null;
XmlDocument xmlDoc;
try
{
// create an instance of the XMLDocument
xmlDoc = new XmlDocument();
// load the XML
xmlDoc.Load(filepath);
t = obj.GetType();
properties = t.GetProperties();
// taking a property at a time ...
foreach (PropertyInfo proc in properties)
{
// if it's writable
if ( proc.CanWrite )
{
try
{
// then match a value from the XML Node
currentValue = getObjectFromParam(proc.Name, proc.PropertyType.ToString(), xmlDoc);
// if there's a value returned,
if ( currentValue != null )
// then assign the value to the property
t.InvokeMember(proc.Name,BindingFlags.Default | BindingFlags.SetProperty,null,
obj,new object [] { currentValue });
}
catch(Exception e)
{
throw e;
}
}
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// This method will extract a value from the XML node. It first select the first
/// xml element that match the xmlName specified in the first parameter. If there's
/// no match, then it will select the first element that has the xmlName specified as
/// its attribute. Then check to see which type it is, and parse/convert/box to that type.
/// </summary>
/// <param name="xmlName">the name of the xml element/attribute to match</param>
/// <param name="proType">the string indicating which type the xmlName should be boxed to</param>
/// <param name="xmlDoc">the xml node</param>
/// <returns>the boxed object containing the value from the xml node</returns>
public static object getObjectFromParam(string xmlName, string proType, XmlDocument xmlDoc)
{
string xmlValue = "";
XmlNode node;
try
{
proType = proType.Replace("System.", "");
// first select the first element of the name xmlName
node = xmlDoc.SelectSingleNode( "//" + xmlName );
if ( node == null )
{
// if no elements match, then look for attributes
node = xmlDoc.SelectSingleNode( "//*[@" + xmlName + "]" );
if ( node != null )
// if there's an element returned, then get its attribute
xmlValue = node.Attributes[ xmlName ].Value;
}
else {
// get the text
xmlValue = node.InnerText;
}
// if nothing is found after trying both element and attribute
if ( node == null )
return null;
// convert, box and return the value of the specific type
if ( proType == "Byte" ) return Byte.Parse( xmlValue.Trim() );
if ( proType == "Char" ) return Char.Parse( xmlValue.Trim() );
if ( proType == "Decimal" ) return Decimal.Parse( xmlValue.Trim() );
if ( proType == "Double" ) return Double.Parse( xmlValue.Trim() );
if ( proType == "Int16" ) return Int16.Parse( xmlValue.Trim() );
if ( proType == "Int32" ) return Int32.Parse( xmlValue.Trim() );
if ( proType == "Int64" ) return Int64.Parse( xmlValue.Trim() );
if ( proType == "SByte" ) return SByte.Parse( xmlValue.Trim() );
if ( proType == "Single" ) return Single.Parse( xmlValue.Trim() );
if ( proType == "UInt16" ) return UInt16.Parse( xmlValue.Trim() );
if ( proType == "UInt32" ) return UInt32.Parse( xmlValue.Trim() );
if ( proType == "UInt64" ) return UInt64.Parse( xmlValue.Trim() );
if ( proType == "DateTime" ) return DateTime.Parse( xmlValue.Trim() );
if ( proType == "String" ) return xmlValue;
if ( proType == "Boolean" )
{
switch( xmlValue.Trim().ToLower() )
{
case "+":
case "1":
case "ok":
case "right":
case "on":
case "true":
case "t":
case "yes":
case "y":
return true;
default:
return false;
}
}
return null;
}
catch
{
return null;
}
}
}
}
Ok, something deserves some explanations now. First, this file defines a namespace DataReflector. And in this namespace, theres a class XMLDocument (Not to confuse you with the XmlDocument class in the System.Xml namespace!). This class defines 2 static methods.
The fillProperties() method will take an object obj and a string filepath. It will then attempt to load the xml file, whose path is specified in the filepath parameter. Then it gets a Type object from obj. The Type object will contain the Type information of obj. That means we know what properties the object has, as well as if they are writable. Then we look at each property, and try to get a value from the xml document by calling the getObjectFromXML() method. If theres a value returned, we then invoke the property and assign the retuned value to it.
The getObjectFromXML() method has 3 parameters. xmlName, which is the name of the property, as well as the name of the XML element or attribute. Remember, we rely on identical naming between the class properties and XML elements/attributes, in order to make the mapping happens. The proType is a string representing the Type of the property. The xmlDoc is the XML node of the root element in the XML file. This method first tries to select the first element of the name xmlName. If theres no element found, then it will try again by matching the element, which has one of the attributes as xmlName. If it still cant find one, then it returns null. Otherwise it will then examine which type the property is, and parse the value from the XML to a specific type. Then return it.
Now, the ofthemonth.xml:
<?xml version = "1.0"?>
<DoublingSalary>
<employee employeeID = "914" >
<status>
<isManager>yes</isManager>
</status>
<name>
<firstName>Tin</firstName>
<lastName>Lam</lastName>
</name>
<contactInfo>
<emailAddress>[email protected]</emailAddress>
<phoneNumber />
</contactInfo>
<bunchInfo>
<bunchLocation bunchLocationID = "4" >
<bunchLocationName>NYC</bunchLocationName>
</bunchLocation>
</bunchInfo>
<hiredDate>9/21/2001</hiredDate>
</employee>
</DoublingSalary>
So we happen to have an employee named Tin Lam, whose record is about to be sent to the accounting department, and have his salary doubled. J Note that the phoneName is an empty tag. Theres also no match for the property departmentID. Then the bunchLocationName is just an extra element. Also note that the structure of the elements does not matter to us. We will only select the first element of the matching elements or attributes name. So number of occurrences and structure does not matter. If there are more than one matches, then we will just look at the first one, the rest will be ignored. If there is no match, then the property will not be involved.
Finally, the test.cs will prove that everything is behaving the way weve expected.
using System;
public class test
{
private test() {}
public static void Main(string[] args)
{
// make sure there is at least one argument
if ( args.Length > 0 )
{
// create the object
BusinessEntity.employee emp = new BusinessEntity.employee();
// then pass the object and the first command line argument
if ( DataReflector.XMLDocument.fillProperties(emp, args[0]) )
Console.WriteLine( emp.ToString() );
else
Console.WriteLine( "error" );
}
else
{
Console.WriteLine( "do you want to feed me a file?" );
}
}
}
You can compile all the three .CS files into one single assembly: csc /t:exe /out:loadxml.exe employee.cs FromXML.cs test.cs
Then run the executable: loadxml ofthemonth.xml
Assuming everything goes smooth; you should see something like the following:
[ReflectingDataXML.jpg]
Conclusion
If you are familiar with how the .NET serialization (supported by the classes in the System.Xml.Serialization namespace) works, you can tell that this reflection technique with XML documents is a little like it. However, unlike the .NET serialization, this technique does not require you to modify the targeting class in anyway. You do not have to specify any attributes in the class, nor do you have to implement any interfaces or methods in order to provide the de-serialization in a customized way. It is actually pretty handy if you just know the data is there, but dont know the structure of it. After all, this example is by no mean to discourage the use of the .NET de-serialization. Its just to show how you can perform data mapping with reflection.