I use XML serialization for the reading of my Config-POCOs.
To get intellisense support in Visual Studio for XML files I need a schema file. I can create the schema with xsd.exe mylibrary.dll and this works fine.
But I want that the schema is always created if I serialize an object to the file system. Is there any way without using xsd.exe?
thank you, this was the right way for me.
solution:
XmlReflectionImporter importer = new XmlReflectionImporter();
XmlSchemas schemas = new XmlSchemas();
XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
Type type = toSerialize.GetType();
XmlTypeMapping map = importer.ImportTypeMapping(type);
exporter.ExportTypeMapping(map);
TextWriter tw = new StreamWriter(fileName + ".xsd");
schemas[0].Write(tw);
tw.Close();
The solution posted above by Will worked wonderfully, except I realized that the schema generated did not reflect the attributes on the different class members. For example a class decorated with serialization hint attributes (see the sample below), would have not rendered correctly.
public class Test
{
[XmlAttribute()]
public string Attribute { get; set; }
public string Description { get; set; }
[XmlArray(ElementName = "Customers")]
[XmlArrayItem(ElementName = "Customer")]
public List<CustomerClass> blah { get; set; }
}
To address this, I created a few helper functions that use reflection to traverse the class hierarchy, read the attributes, and populate a XmlAttributeOverrides object that can be passed into the XmlReflectionImporter.
public static void AttachXmlAttributes(XmlAttributeOverrides xao, Type t)
{
List<Type> types = new List<Type>();
AttachXmlAttributes(xao, types, t);
}
public static void AttachXmlAttributes(XmlAttributeOverrides xao, List<Type> all, Type t)
{
if(all.Contains(t))
return;
else
all.Add(t);
XmlAttributes list1 = GetAttributeList(t.GetCustomAttributes(false));
xao.Add(t, list1);
foreach (var prop in t.GetProperties())
{
XmlAttributes list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, prop.PropertyType);
}
}
private static XmlAttributes GetAttributeList(object[] attributes)
{
XmlAttributes list = new XmlAttributes();
foreach (var attribute in attributes)
{
Type type = attribute.GetType();
if (type.Name == "XmlAttributeAttribute") list.XmlAttribute = (XmlAttributeAttribute)attribute;
else if (type.Name == "XmlArrayAttribute") list.XmlArray = (XmlArrayAttribute)attribute;
else if (type.Name == "XmlArrayItemAttribute") list.XmlArrayItems.Add((XmlArrayItemAttribute)attribute);
}
return list;
}
public static string GetSchema<T>()
{
XmlAttributeOverrides xao = new XmlAttributeOverrides();
AttachXmlAttributes(xao, typeof(T));
XmlReflectionImporter importer = new XmlReflectionImporter(xao);
XmlSchemas schemas = new XmlSchemas();
XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
XmlTypeMapping map = importer.ImportTypeMapping(typeof(T));
exporter.ExportTypeMapping(map);
using (MemoryStream ms = new MemoryStream())
{
schemas[0].Write(ms);
ms.Position = 0;
return new StreamReader(ms).ReadToEnd();
}
}
Hope this helps someone else.
Look at the System.Xml.Serialization.XmlSchemaExporter class. I can't recall the exact details, but there is enough functionality in that namespace to do what you require.
Improvement to Matt Murrell version: to apply XmlAttributes recursively for nested property user type (for example CustomerClass property).
private static void AttachXmlAttributes(XmlAttributeOverrides xao, List<Type> all, Type t)
{
if (all.Contains(t))
{
return;
}
else
{
all.Add(t);
}
var list1 = GetAttributeList(t.GetCustomAttributes(false));
xao.Add(t, list1);
foreach (var prop in t.GetProperties())
{
var propType = prop.PropertyType;
if (propType.IsGenericType) // is list?
{
var args = propType.GetGenericArguments();
if (args != null && args.Length == 1)
{
var genType = args[0];
if (genType.Name.ToLower() != "object")
{
var list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, genType);
}
}
}
else
{
var list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, prop.PropertyType);
}
}
}
private static XmlAttributes GetAttributeList(object[] attributes)
{
var list = new XmlAttributes();
foreach (var attr in attributes)
{
Type type = attr.GetType();
switch (type.Name)
{
case "XmlAttributeAttribute":
list.XmlAttribute = (XmlAttributeAttribute)attr;
break;
case "XmlRootAttribute":
list.XmlRoot = (XmlRootAttribute)attr;
break;
case "XmlElementAttribute":
list.XmlElements.Add((XmlElementAttribute)attr);
break;
case "XmlArrayAttribute":
list.XmlArray = (XmlArrayAttribute)attr;
break;
case "XmlArrayItemAttribute":
list.XmlArrayItems.Add((XmlArrayItemAttribute)attr);
break;
}
}
return list;
}
Related
I am using XmlSerializer to serialize C# objects to XML. I have DefaultValueAttribute on some of the properties of the classes I am trying to serialize, when I try to serialize them it seems that XmlSerializer does not include value in xml if it equals default value.
Look at this example:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
public class Person
{
[System.Xml.Serialization.XmlAttribute()]
[System.ComponentModel.DefaultValue("John")]
public string Name { get; set; }
}
public static class Test
{
public static void Main()
{
var serializer = new XmlSerializer(typeof(Person));
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
}
}
}
It will produce the following xml (notice name attribute is not available):
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
I cannot modify the source code of the classes so I CANNOT remove DefaultValueAttribute. Is there a way to make XmlSerializer serialize this properties without changing the source code?
You can do it by passing in an XmlAttributeOverrides instance to the XmlSerializer when you create it as below. You can either change the default value to something else using this, or set it to null to effectively remove it.
XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();
var attributes = new XmlAttributes()
{
XmlDefaultValue = null,
XmlAttribute = new XmlAttributeAttribute()
};
attributeOverrides.Add(typeof(Person), "Name", attributes);
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
Update: the above means you have to provide other unrelated attributes again on each property you are changing. This is a bit of a chore if you have a lot of properties and just want to remove default for all of them. The class below can be used to preserve other custom attributes while only removing one type of attribute. It can be further extended if required to only do it for certain properties etc.
public class XmlAttributeOverrideGenerator<T>
{
private static XmlAttributeOverrides _overrides;
private static Type[] _ignoreAttributes = new Type[] { typeof(DefaultValueAttribute) };
static XmlAttributeOverrideGenerator()
{
_overrides = Generate();
}
public static XmlAttributeOverrides Get()
{
return _overrides;
}
private static XmlAttributeOverrides Generate()
{
var xmlAttributeOverrides = new XmlAttributeOverrides();
Type targetType = typeof(T);
foreach (var property in targetType.GetProperties())
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
xmlAttributeOverrides.Add(targetType, property.Name, propertyAttributes);
}
return xmlAttributeOverrides;
}
public class CustomAttribProvider : ICustomAttributeProvider
{
private PropertyInfo _prop = null;
private Type[] _ignoreTypes = null;
public CustomAttribProvider(PropertyInfo property, params Type[] ignoreTypes)
{
_ignoreTypes = ignoreTypes;
_prop = property;
}
public object[] GetCustomAttributes(bool inherit)
{
var attribs = _prop.GetCustomAttributes(inherit);
if (_ignoreTypes == null) return attribs;
return attribs.Where(attrib => IsAllowedType(attrib)).ToArray();
}
private bool IsAllowedType(object attribute)
{
if (_ignoreTypes == null) return true;
foreach (Type type in _ignoreTypes)
if (attribute.GetType() == type)
return false;
return true;
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
public bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
}
}
Usage:
XmlAttributeOverrides attributeOverrides = XmlAttributeOverrideGenerator<Person>.Get();
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
I dont have enough reputation to comment on steve16351's answer. but I feel I improved upon his code a little bit.
My use case involved an XML schema file generated via XSD.exe provided by Microsoft, and a class was then generated from the schema file. So the class had all the DefaultValue tags on it from the schema. There was also many nested types created. So to make my code succinct, I modified the 'Generate' method to get the overrides for the top-level class, and all the ones beneath it. Then everything is returned in a single XmlAttributesOverrides object.
static XmlAttributeOverrideGenerator()
{
_overrides = Generate(typeof(T), new XmlAttributeOverrides());
}
private static XmlAttributeOverrides Generate(Type targetType, XmlAttributeOverrides Overrides)
{
foreach (var property in targetType.GetProperties())
{
if (property.CustomAttributes.Any( CA => CA.AttributeType == typeof(DefaultValueAttribute)))
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
Overrides.Add(targetType, property.Name, propertyAttributes);
}
else
{
Type propType = property.PropertyType;
if (propType.IsClass && !propType.IsValueType && !propType.IsEnum && !propType.IsPrimitive && !propType.IsSealed) // Check if this is a custom class
{
//If this is a nested class or other class type, generate its overrides as well
Generate(propType, Overrides);
}
}
}
return Overrides;
}
Given an XElement input:
<?xml version="1.0"?>
<Item Number="100" ItemName="TestName1" ItemId="1"/>
with an Item model like:
public class Item
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public int? Number { get; set; }
// public DateTime? Created {get; set;}
}
Why does this code:
public static T DeserializeObject<T>(XElement element) where T : class, new()
{
try
{
var serializer = new XmlSerializer(typeof(T));
var x = (T)serializer.Deserialize(element.CreateReader());
return x;
}
catch
{
return default(T);
}
}
Returns an Item model with default values: ItemId=0, ItemName=null, Number=null instead of the correct values.
This can be fixed by adding attributes to the model [XmlAttribute("ItemName")] but I do not want to require XmlAttributes.
The addition of a nullable DateTime field to the Item model also causes a deserialization exception, even if it has an XmlAttribute.
I have the equivalent code in JSON.net where all I am doing is p.ToObject() on the JToken to deserialize it to an Item object. Is there another technique or deserializer that handles this better without having to qualify with attributes, etc. and that handles nullable values. It should be that easy for the XML version too.
Please consider this question carefully, as I had another similar question closed as [Duplicate] where none of the answers actually covered what I was asking.
It seems if you don't decorate your C# class properties with the XML attributes, the deserializer assumes the properties are elements and not attributes.
string xml = "<Item Number=\"100\" ItemName=\"TestName1\" ItemId=\"1\"><Number>999</Number></Item>";
XElement el = XElement.Parse(xml);
var obj = DeserializeObject<Item>(el); // Returns Number equal to 999 while others are left null or 0
You can try Cinchoo ETL - an open source lib for your needs.
string xml = #"<?xml version=""1.0""?>
<Item Number = ""100"" ItemName = ""TestName1"" ItemId = ""1"" />";
XDocument doc = XDocument.Parse(xml);
var item = ChoXmlReader<Item>.LoadXElements(new XElement[] { doc.Root }).FirstOrDefault();
Console.WriteLine($"ItemId: {item.ItemId}");
Console.WriteLine($"ItemName: {item.ItemName}");
Console.WriteLine($"Number: {item.Number}");
Console.WriteLine($"Created: {item.Created}");
Hope it helps.
Disclaimer: I'm the author of this library.
I ended up writing the custom deserializer below that is similar to the json deserializer method for jToken. It should work for basic, flat objects that have simple type properties like string, int, datetime, etc. and the nullable versions of those types. It does not require XmlAttributes.
public static T ToOject<T>(this XElement element) where T : class, new()
{
try
{
T instance = new T();
foreach (var property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var xattribute = element.Attribute(property.Name);
var xelement = element.Element(property.Name);
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var value = xattribute?.Value ?? xelement.Value;
try
{
if (value != null)
{
if (property.CanWrite)
{
property.SetValue(instance, Convert.ChangeType(value, propertyType));
}
}
}
catch // (Exception ex) // If Error let the value remain default for that property type
{
Console.WriteLine("Not able to parse value " + value + " for type '" + property.PropertyType + "' for property " + property.Name);
}
}
return instance;
}
catch (Exception ex)
{
return default(T);
}
}
When you know what is, you can write the following:
var list = xdocument.Descendants("Item")
.Select(p => p => p.ToOject<T>())
.ToList();
I had a similar problem with an object consisting of around 30 nested objects. Most of the properties were to be serialized as attributes. I did use XmlAttributeOverrides, but the code is simple enough. Basically I get all the properties from all the classes in a a specific namespace, leave only simple type properties (value type and strings) and then filter out all the properties that already have a Serialization attribute. Then I apply the XmlAttribute to all these properties. In my case there will be no struct types so that's not a problem, but you might have to filter out structs too.
//Note that the serializer is created just once in the application; this is to prevent
//a known memory leak with the XmlSerializer when using
//the constructor that takes XmlAttributeOverrides
private static XmlSerializer serializer = new XmlSerializer(typeof(MyClass), GetAttributeOverrides());
public static MyClass GetObject(string xml)
{
using (var reader = new StringReader(xml))
{
return (MyClass)serializer.Deserialize(reader);
}
}
public static XmlAttributeOverrides GetAttributeOverrides()
{
var overrides = new XmlAttributeOverrides();
var simpleProperties = (from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.Namespace == typeof(MyClass).Namespace
from p in t.GetProperties()
where IsSimpleProperty(p)
select p);
foreach (var prop in simpleProperties)
{
var attrs = new XmlAttributes() { XmlAttribute = new XmlAttributeAttribute() };
overrides.Add(prop.DeclaringType, prop.Name, attrs);
}
return overrides;
}
public static bool IsSimpleProperty(PropertyInfo prop)
{
return (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string))
&& !prop.CustomAttributes.Any(a => a.AttributeType.Namespace == "System.Xml.Serialization");
}
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?
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.
[update] to show usage
Create the XML Schema either manually or using the XSD.exe (you will need the Windows SDK). See the Examples.
create a sample.xml file, content is your example
locate and run the XSD tool xsd sample.xml /outputDir:XmlSerialization
a .xsd file should be generated, you may want to modify some element types
for example SecurityGroup.Id is maybe auto-generated as xs:string, change it to i.e. xs:int
or remove the minOccurs="0" at SecurityUser.userId if you know that the property is always set
you maybe have to fix SecurityGroup.SecurityUsers from
<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">
run the XSD tool using the XSD xsd sample.xsd /outputDir:XmlSerialization /c
a .cs file should be generated, verify it
I needed to fix the XSD manually because i.e. SecurityGroup.SecurityUser was generated as a 2-dim array
use the auto-generated classes in your project / script
create a XmlSerializer - see Example - and deserialize the XML string, access the data afterwards
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;
}
see my manually fixed XSD here, verified working with your sample data
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!
I store SQL results into a dynamic List, of which has an underlying DapperRow type. I am trying to serialize/unserialize this List of which XMLserializer complains:
There was an error generating the XML document. ---> System.InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. Dapper.SqlMapper+DapperRow does not implement Add(System.Object).
Is there a way around this (besides the obvious casting the results to my own concrete object), or is it possible to somehow make DapperRow objects conform to System.Xml.XMLserializer constraints?
It states my result array is System.Collections.Generic.List<dynamic> {System.Collections.Generic.List<object>}
Opening the array it says each object is of type object {Dapper.SqlMapper.DapperRow}
I think because DapperRows are now basically IDictionary<string, object> that XML is having issues (I cannot use anything but System.Xml.XmlSerializer so don't suggest an alternative).
I just want to be able to turn a List<dynamic> that I get from Dapper and serialize and deserialize correctly using System.Xml.XmlSerializer
I was able to get something at least serializable by using a serializable dictionary : http://weblogs.asp.net/pwelter34/444961
var results = conn.Query<dynamic>(sql, param);
var resultSet = new List<SerializableDictionary<string, object>>();
foreach (IDictionary<string, object> row in results)
{
var dict = new SerializableDictionary<string, object>();
foreach (var pair in row)
{
dict.Add(pair.Key, pair.Value);
}
resultSet.Add(dict);
}
Its ugly, so I hope more elegant solutions come up
is it possible to somehow make DapperRow objects conform to System.Xml.XMLserializer constraints?
I don't think that this is possible. The DapperRow class is private and it does not have a parameterless constructor.
However, you might be able to use other means to fix your problem.
I suggest that you put your complex serialization logic behind an extension method. This way your original code will stay clean.
I also suggest the following model for storing the data (although you can choose not to use it and use your own logic behind the extension method):
public class Cell
{
public string Name { get; set; }
public object Value { get; set; }
public Cell(){}
public Cell(KeyValuePair<string, object> kvp)
{
Name = kvp.Key;
Value = kvp.Value;
}
}
public class Row : List<Cell>
{
public Row(){}
public Row(IEnumerable<Cell> cells)
: base(cells){}
}
public class Rows : List<Row>
{
public Rows(){}
public Rows(IEnumerable<Row> rows )
:base(rows){}
}
And then the extension method should look something like this:
public static class Extensions
{
public static void Serialize(this IEnumerable<dynamic> enumerable, Stream stream)
{
var rows =
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
}
}
Then you would be able to use this code:
var result = connection.Query("SELECT * From Customers");
var memory_stream = new MemoryStream();
result.Serialize(memory_stream);
See how this code is very small because all the complex logic is moved to the extension method.
The model that I suggested allows also for deserialization, just make sure that you use the correct type (e.g. Rows) like this:
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
Rows rows = (Rows)serializer.Deserialize(stream);
You can also have an extension method that just converts the resultset of Dapper to the Rows type and handle the serialization of Rows your self. Such extension method should look something like this:
public static Rows ToRows(this IEnumerable<dynamic> enumerable)
{
return
new Rows(
enumerable
.Cast<IEnumerable<KeyValuePair<string, object>>>()
.Select(row =>
new Row(row.Select(cell => new Cell(cell)))));
}
And then use it like this:
var rows = connection.Query("SELECT * From Customers").ToRows();
XmlSerializer serializer = new XmlSerializer(typeof(Rows));
serializer.Serialize(stream, rows);
Firstly, decorate the DapperResultSet with a [Serializable] attribute. Also create a constructor and in that, assign Rows with an empty List<object>. Use this modified code: (it also contains a modified implemented method)
[Serializable]
public class DapperResultSet : IEnumerable<object>
{
public List Rows { get; set; }
public void Add(dynamic o)
{
Rows.Add(o);
}
public DapperResultSet()
{
Rows = new List<object>();
}
public IEnumerator<object> GetEnumerator()
{
return Rows.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Next in your event handler (or where you would like to do the serialization):
var results = conn.Query<dynamic>(sql, param);
var r = new DapperResultSet();
foreach (var row in results)
{
r.Add(row);
}
//Here is the serialization part:
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
xs.Serialize(new FileStream("Serialized.xml", FileMode.Create), r); //Change path if necessary
For Deserialization,
XmlSerializer xs = new XmlSerializer(typeof(DapperResultSet));
DapperResultSet d_DRS = xs.Deserialize(new FileStream("Serialized.xml", FileMode.Open)); //Deserialized
Challenging request, because Dapper is not designed to be serializable. But let see what can be done.
The first decision is easy - we need to implement IXmlSerializable. The question is how.
Serialization is not a big deal, since we have the field names and values. So we could use similar approach to the SerializableDictionary<TKey, TValue> you mentioned. However, it heavily relies on typeof(TKey) and typeof(TValue)'. We have no problem with key (it's a string), but the type of the value is object. As I mentioned, it's not a problem to write an object value as XML. The problem is deserialization. At that point, all we have is a string and no any clue what that string is. Which means we need to store some metadata in order to be able to deserialize correctly. Of course there are many ways to do that, but I decided to store field names and types separately at the beginning, and then items with values only.
Putting it all together, here is what I ended up:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Samples
{
public class DapperResultSet : IXmlSerializable
{
static readonly Type TableType;
static readonly Type RowType;
static readonly Func<object, string[]> GetFieldNames;
static readonly Func<object, object[]> GetFieldValues;
static readonly Func<string[], object> CreateTable;
static readonly Func<object, object[], object> CreateRow;
static DapperResultSet()
{
TableType = typeof(Dapper.SqlMapper).GetNestedType("DapperTable", BindingFlags.NonPublic);
RowType = typeof(Dapper.SqlMapper).GetNestedType("DapperRow", BindingFlags.NonPublic);
// string[] GetFieldNames(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, string[]>>(
Expression.Field(Expression.Field(Expression.Convert(row, RowType), "table"), "fieldNames"),
row);
GetFieldNames = expr.Compile();
}
// object[] GetFieldValues(object row)
{
var row = Expression.Parameter(typeof(object), "row");
var expr = Expression.Lambda<Func<object, object[]>>(
Expression.Field(Expression.Convert(row, RowType), "values"),
row);
GetFieldValues = expr.Compile();
}
// object CreateTable(string[] fieldNames)
{
var fieldNames = Expression.Parameter(typeof(string[]), "fieldNames");
var expr = Expression.Lambda<Func<string[], object>>(
Expression.New(TableType.GetConstructor(new[] { typeof(string[]) }), fieldNames),
fieldNames);
CreateTable = expr.Compile();
}
// object CreateRow(object table, object[] values)
{
var table = Expression.Parameter(typeof(object), "table");
var values = Expression.Parameter(typeof(object[]), "values");
var expr = Expression.Lambda<Func<object, object[], object>>(
Expression.New(RowType.GetConstructor(new[] { TableType, typeof(object[]) }),
Expression.Convert(table, TableType), values),
table, values);
CreateRow = expr.Compile();
}
}
static readonly dynamic[] emptyItems = new dynamic[0];
public IReadOnlyList<dynamic> Items { get; private set; }
public DapperResultSet()
{
Items = emptyItems;
}
public DapperResultSet(IEnumerable<dynamic> source)
{
if (source == null) throw new ArgumentNullException("source");
Items = source as IReadOnlyList<dynamic> ?? new List<dynamic>(source);
}
XmlSchema IXmlSerializable.GetSchema() { return null; }
void IXmlSerializable.WriteXml(XmlWriter writer)
{
if (Items.Count == 0) return;
// Determine field names and types
var fieldNames = GetFieldNames((object)Items[0]);
var fieldTypes = new TypeCode[fieldNames.Length];
for (int count = 0, i = 0; i < Items.Count; i++)
{
var values = GetFieldValues((object)Items[i]);
for (int c = 0; c < fieldTypes.Length; c++)
{
if (fieldTypes[i] == TypeCode.Empty && values[c] != null)
{
fieldTypes[i] = Type.GetTypeCode(values[c].GetType());
if (++count >= fieldTypes.Length) break;
}
}
}
// Write fields
writer.WriteStartElement("Fields");
writer.WriteAttributeString("Count", XmlConvert.ToString(fieldNames.Length));
for (int i = 0; i < fieldNames.Length; i++)
{
writer.WriteStartElement("Field");
writer.WriteAttributeString("Name", fieldNames[i]);
writer.WriteAttributeString("Type", XmlConvert.ToString((int)fieldTypes[i]));
writer.WriteEndElement();
}
writer.WriteEndElement();
// Write items
writer.WriteStartElement("Items");
writer.WriteAttributeString("Count", XmlConvert.ToString(Items.Count));
foreach (IDictionary<string, object> item in Items)
{
writer.WriteStartElement("Item");
foreach (var entry in item)
{
writer.WriteStartAttribute(entry.Key);
writer.WriteValue(entry.Value);
writer.WriteEndAttribute();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.MoveToContent();
bool isEmptyElement = reader.IsEmptyElement;
reader.ReadStartElement(); // Container
if (isEmptyElement) return;
// Read fields
int fieldCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Fields");
var fieldNames = new string[fieldCount];
var fieldTypes = new TypeCode[fieldCount];
var fieldIndexByName = new Dictionary<string, int>(fieldCount);
for (int c = 0; c < fieldCount; c++)
{
fieldNames[c] = reader.GetAttribute("Name");
fieldTypes[c] = (TypeCode)XmlConvert.ToInt32(reader.GetAttribute("Type"));
fieldIndexByName.Add(fieldNames[c], c);
reader.ReadStartElement("Field");
}
reader.ReadEndElement();
// Read items
int itemCount = XmlConvert.ToInt32(reader.GetAttribute("Count"));
reader.ReadStartElement("Items");
var items = new List<dynamic>(itemCount);
var table = CreateTable(fieldNames);
for (int i = 0; i < itemCount; i++)
{
var values = new object[fieldCount];
if (reader.MoveToFirstAttribute())
{
do
{
var fieldName = reader.Name;
var fieldIndex = fieldIndexByName[fieldName];
values[fieldIndex] = Convert.ChangeType(reader.Value, fieldTypes[fieldIndex], CultureInfo.InvariantCulture);
}
while (reader.MoveToNextAttribute());
}
reader.ReadStartElement("Item");
var item = CreateRow(table, values);
items.Add(item);
}
reader.ReadEndElement(); // Items
reader.ReadEndElement(); // Container
Items = items;
}
}
}
Some things would have been easier if we modify the Dapper source code, but I assume you don't want to do that.
And here is a sample usage:
static void Test(IEnumerable<dynamic> source)
{
var stream = new MemoryStream();
var sourceSet = new DapperResultSet(source);
var serializer = new XmlSerializer(typeof(DapperResultSet));
serializer.Serialize(stream, sourceSet);
stream.Position = 0;
var reader = new StreamReader(stream);
var xml = reader.ReadToEnd();
stream.Position = 0;
var deserializer = new XmlSerializer(typeof(DapperResultSet));
var target = ((DapperResultSet)deserializer.Deserialize(stream)).Items;
}
I have a class with 3 List collections like the following.
I am trying to have a logic which will iterate through the object's "collection"
properties and do some operation using the data stored in those collections.
I am just wondering if there is an easy way of doing it using foreach.
thanks
public class SampleChartData
{
public List<Point> Series1 { get; set; }
public List<Point> Series2 { get; set; }
public List<Point> Series3 { get; set; }
public SampleChartData()
{
Series1 = new List<Point>();
Series2 = new List<Point>();
Series3 = new List<Point>();
}
}
Function to get all IEnumerable<T> from object:
public static IEnumerable<IEnumerable<T>> GetCollections<T>(object obj)
{
if(obj == null) throw new ArgumentNullException("obj");
var type = obj.GetType();
var res = new List<IEnumerable<T>>();
foreach(var prop in type.GetProperties())
{
// is IEnumerable<T>?
if(typeof(IEnumerable<T>).IsAssignableFrom(prop.PropertyType))
{
var get = prop.GetGetMethod();
if(!get.IsStatic && get.GetParameters().Length == 0) // skip indexed & static
{
var collection = (IEnumerable<T>)get.Invoke(obj, null);
if(collection != null) res.Add(collection);
}
}
}
return res;
}
Then you can use something like
var data = new SampleChartData();
foreach(var collection in GetCollections<Point>(data))
{
foreach(var point in collection)
{
// do work
}
}
to iterate through all elements.
Use Reflection to get the objects Properties. Then iterate over those to see is IEnumerable<T>. Then iterate over the IEnumerable properties
You can use reflection to get the list of properties from the object. This example gets all the properties and prints their name and Count to the console:
public static void PrintSeriesList()
{
SampleChartData myList = new SampleChartData();
PropertyInfo[] Fields = myList.GetType().GetProperties();
foreach(PropertyInfo field in Fields)
{
var currentField = field.GetValue(myList, null);
if (currentField.GetType() == typeof(List<Point>))
{
Console.WriteLine("List {0} count {1}", field.Name, ((List<Point>)currentField).Count);
}
}
}
Just found a quick solution, but maybe some of you have better ways of doing it.
this is what I did.
SampleChartData myData = DataFeed.GetData();
Type sourceType = typeof(SampleChartData);
foreach (PropertyInfo pi in (sourceType.GetProperties()))
{
if (pi.GetValue(myData, null).GetType() == typeof(List<Point>))
{
List<Point> currentSeriesData = (List<Point>)pi.GetValue(myData, null);
// then do something with the data
}
}