Reputation: 1017
I have a class that goes and gets data from a database and it gets the as xml data in the form of hierarchical data.
below is a sample of the data:
<SecurityGroups>
<SecurityGroup>
<Id>1</Id>
<Name>View</Name>
</SecurityGroup>
<SecurityGroup>
<Id>2</Id>
<Name>Fill</Name>
<SecurityUsers>
<SecurityUser>
<securityId>2</securityId>
<userId>2</userId>
<username>Fill</username>
</SecurityUser>
<SecurityUser>
<securityId>2</securityId>
<userId>3</userId>
<username>FillOne</username>
</SecurityUser>
<SecurityUser>
<securityId>2</securityId>
<userId>4</userId>
<username/></SecurityUser>
</SecurityUsers>
</SecurityGroup>
<SecurityGroup>
<Id>3</Id>
<Name>Update</Name>
<SecurityUsers>
<SecurityUser>
<securityId>3</securityId>
<userId>5</userId>
<username>Update</username>
</SecurityUser>
<SecurityUser>
<securityId>3</securityId>
<userId>6</userId>
<username>UpdateOne</username>
</SecurityUser>
</SecurityUsers>
</SecurityGroup>
<SecurityGroup>
<Id>4</Id>
<Name>Admin</Name>
<SecurityUsers>
<SecurityUser>
<securityId>4</securityId>
<userId>1</userId>
<username>JTays</username>
</SecurityUser>
</SecurityUsers>
</SecurityGroup>
Which works great! Now, I have a class that uses reflection to convert from xml to models that i am eventually going to use in a treeview. below is the entire class.
the models are:
[XmlRootAttribute(Namespace = "", IsNullable = false)]
public class SecurityGroup
{
[XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
public int Id { get; set; }
[XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
public string Name { get; set; }
[XmlElementAttribute("SecurityUser",
Form = XmlSchemaForm.Unqualified)]
public List<SecurityUser> SecurityUsers { get; set; }
}
public class SecurityUser:IEntity
{
[XmlElement(Form = XmlSchemaForm.Unqualified)]
public int SecurityId { get; set; }
[XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
public int UserId { get; set; }
[XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
public string Username { get; set; }
public int Id { get; set; }
}
Here is the class that converts.
public class AdminManager : IAdminService
{
public IEnumerable<SecurityGroup> SecurityGroups()
{
IEnumerable<SecurityGroup> list = null;
XmlDocument xmlDoc = new XmlDocument();
using (var c = new FMContext())
{
var xmlData = c.Database.SqlQuery<string>("AllSecurtyUsersProc").FirstOrDefault();
if (xmlData != null)
{
xmlDoc.LoadXml(xmlData);
list = ConvertXmlToClass<SecurityGroup>(xmlDoc, "/SecurityGroups/SecurityGroup");
}
}
return list;
}
public static IEnumerable<T> ConvertXmlToClass<T>( XmlDocument doc, string nodeString)
where T:class, new()
{
var xmlNodes = doc.SelectNodes(nodeString);
List<T> list = new List<T>();
foreach (XmlNode node in xmlNodes)
{
var item = GetNewItem<T>(node);
list.Add(item);
}
return list;
}
public static T GetNewItem<T>(XmlNode node)
where T:class, new()
{
var type = typeof (T);
var item = new T();
var properties = type.GetProperties();
foreach (var property in properties)
{
var propertyType = property.PropertyType;
var propertyName = property.Name;
object value = null;
if (IsEnumerable(property))
{
value = GetNodeCollectionValue(property,node);
}
else
{
value = GetNodeValue(node, propertyName);
}
if (value!=null)
{
property.SetValue(item, Convert.ChangeType(value, propertyType), null);
}
}
return item;
}
private static object GetNodeCollectionValue(PropertyInfo property, XmlNode node)
{
var doc = new XmlDocument();
var itemType = property.PropertyType.GenericTypeArguments[0];
var xml = $"<{property.Name}><{itemType.Name}>{node.InnerXml}</{itemType.Name}></{property.Name}>";
doc.LoadXml(xml);
if (itemType != null)
{
var type = typeof (AdminManager);
var methodInfo = type.GetMethod("ConvertXmlToClass");
if (methodInfo!=null)
{
var method = methodInfo.MakeGenericMethod(itemType);
if (method != null)
{
object[] args = {doc, property.Name};
object result = method.Invoke(null, args);
return result;
}
}
}
return new object();
}
private static bool IsEnumerable(PropertyInfo property)
{
var type = property.PropertyType;
return typeof (IEnumerable).IsAssignableFrom(type) && type.IsGenericType;
}
private static object GetNodeValue(XmlNode node, string nodeName)
{
var i = node[nodeName]?.InnerText;
return i ?? null;
}
}
Okay, so now my issue. My problem is that when it converts it doesn't get the correct data for the SecurtyUsers
classes it adds them as an object, but everything is null or 0. Can someone help me figure out where i went worng?
Upvotes: 0
Views: 617
Reputation: 1017
I found the issue, it was that i was passing the entire xml into the ConvertXmlToClass<T>
method. I have fixed this and below is the working code.
public class AdminManager : IAdminService
{
public IEnumerable<SecurityGroup> SecurityGroups()
{
IEnumerable<SecurityGroup> list = null;
XmlDocument xmlDoc = new XmlDocument();
using (var c = new FMContext())
{
var xmlData = c.Database.SqlQuery<string>("AllSecurtyUsersProc").FirstOrDefault();
if (xmlData != null)
{
xmlDoc.LoadXml(xmlData);
list = ConvertXmlToClass<SecurityGroup>(xmlDoc, "/SecurityGroups/SecurityGroup");
}
}
return list;
}
public static IEnumerable<T> ConvertXmlToClass<T>(XmlDocument doc, string nodeString)
where T : class, new()
{
var xmlNodes = doc.SelectNodes(nodeString);
List<T> list = new List<T>();
foreach (XmlNode node in xmlNodes)
{
var item = GetNewItem<T>(node);
list.Add(item);
}
return list;
}
public static T GetNewItem<T>(XmlNode node)
where T : class, new()
{
var type = typeof(T);
var item = new T();
var properties = type.GetProperties();
foreach (var property in properties)
{
var propertyType = property.PropertyType;
var propertyName = property.Name;
object value = null;
if (IsEnumerable(property))
{
value = GetNodeCollectionValue(property, node);
}
else
{
value = GetNodeValue(node, propertyName);
}
if (value != null)
{
property.SetValue(item, Convert.ChangeType(value, propertyType), null);
}
}
return item;
}
private static object GetNodeCollectionValue(PropertyInfo property, XmlNode node)
{
var doc = new XmlDocument();
var itemType = property.PropertyType.GenericTypeArguments[0];
var xml = node.InnerXml;
if (xml.Contains(property.Name))
{
var start = xml.IndexOf($"<{property.Name}>");
var length = xml.IndexOf($"</{property.Name}>") - start + ($"</{property.Name}>").Length;
xml = xml.Substring(start, length);
doc.LoadXml(xml);
if (itemType != null)
{
var type = typeof(AdminManager);
var methodInfo = type.GetMethod("ConvertXmlToClass");
if (methodInfo != null)
{
var method = methodInfo.MakeGenericMethod(itemType);
if (method != null)
{
object[] args = { doc, $"/{property.Name}/{itemType.Name}" };
object result = method.Invoke(null, args);
var r = result as IEnumerable<object>;
return r;
}
}
}
}
return null;
}
private static bool IsEnumerable(PropertyInfo property)
{
var type = property.PropertyType;
return typeof(IEnumerable).IsAssignableFrom(type) && type.IsGenericType;
}
private static object GetNodeValue(XmlNode node, string nodeName)
{
if (node[nodeName] != null)
{
var i = node[nodeName].InnerText;
return i;
}
return null;
}
}
I hope this helps someone else!
Upvotes: 0
Reputation: 316
First: your example XML doesn't include the closing tag </SecurityGroups>
.
In your GetNodeCollectionValue you pass property.Name
but shouldn't it be property.Name + "/" + itemType.Name
because you want to access the children of that node?
Additionally node.InnerXml
is not traversed, you pass the same node again and again.
I highly recommend using the .Net XML deserializer instead of reinventing the wheel.
Generate a XML schema for your XML example. Afterwards create the .Net classes from the XSD. Pass your XML from the server to a StringReader and Deserialize using the reader. Then just access the SecurityGroup
from the class instance.
Create the XML Schema either manually or using the XSD.exe
(you will need the Windows SDK). See the Examples.
xsd sample.xml /outputDir:XmlSerialization
.xsd
file should be generated, you may want to modify some element types
xs:string
, change it to i.e. xs:int
minOccurs="0"
at SecurityUser.userId if you know that the property is always set<xs:element name="SecurityUsers" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="SecurityUser" minOccurs="0" maxOccurs="unbounded">
to
<xs:element name="SecurityUsers" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="SecurityUser" maxOccurs="unbounded">
xsd sample.xsd /outputDir:XmlSerialization /c
.cs
file should be generated, verify it
SecurityGroups deserialized;
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SecurityGroups));
using (var stringReader = new System.IO.StringReader(xmlData))
{
deserialized = serializer.Deserialize(stringReader) as SecurityGroups;
}
Upvotes: 1