Converting xml to hierarchical data in c# with classes - c#

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!

Related

How to serialize properties with DefaultValueAttribute using XmlSerializer?

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;
}

A Better XElement to Object Without XMLAttributes in C#

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");
}

Trying to get MetadataType by reflect not giving same results if I call the class directly

I'm trying to setup a T4 template that will loop through entity objects and ignore certain navigation properties based on an custom attribute I setup in the class' metdata data.
Here's the setup of the metadata tag
[MetadataType(typeof(ApplicationIntegrationMetadata))]
public partial class ApplicationIntegration
{
}
public class ApplicationIntegrationMetadata
{
[NotToCrud]
public ICollection<Profile> Profiles { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class NotToCrud : System.Attribute
{
}
I've created an extension method based that checks if the class has a specific attribute on the property
public static bool CheckIfPropertyIsAnnotated<T>(this object ctx, string propName)
{
var returnValue = false;
if (!string.IsNullOrWhiteSpace(propName))
{
var thisType = ctx.GetType();
var metadataTypes = thisType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
var metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
var properties = metadata.MetadataClassType.GetProperties();
var found = properties.FirstOrDefault(d => d.Name == propName);
if (found != null)
{
returnValue = Attribute.IsDefined(found, typeof(T));
}
}
}
return returnValue;
}
I've created two separate unit tests to find my issue.
This one works.
[TestCase("Profiles", Result = true, TestName = "Valid property")]
public bool HasAttribute(string propName)
{
var test = new ApplicationIntegration();
var actual = test.CheckIfPropertyIsAnnotated<NotToCrud>(propName);
return actual;
}
This one returns false
[Test]
public void HasAttributeWithAssembly()
{
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (var item in myTypes)
{
var actual = item.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
Console.WriteLine($"{item.Name} - {actual.ToString()}");
Assert.AreEqual(true, actual);
}
}
the problem seems to be with (var thisType = ctx.GetType())
On the second test, it returns
object.GetType returned
{Name = "RuntimeType" FullName = "System.RuntimeType"} System.RuntimeType
instead of
{Name = "ApplicationIntegration" FullName = "Apm.Model.ApplicationIntegration"}
Any clue how to get around this?
Because item is a Type (with value typeof(ApplicationIntegration)), not an ApplicationIntegration. You can create an object with Activator.CreateInstance. Like:
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (Type type in myTypes)
{
object obj = Activator.CreateInstance(type);
var actual = obj.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");

Dynamic override of ToString() using Reflection

I generally override the ToString() method to output the property names and the values associated to them. I got a bit tired of writing these by hand so I'm looking for a dynamic solution.
Main:
TestingClass tc = new TestingClass()
{
Prop1 = "blah1",
Prop2 = "blah2"
};
Console.WriteLine(tc.ToString());
Console.ReadLine();
TestingClass:
public class TestingClass
{
public string Prop1 { get; set; }//properties
public string Prop2 { get; set; }
public void Method1(string a) { }//method
public TestingClass() { }//const
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (Type type in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
{
foreach (System.Reflection.PropertyInfo property in type.GetProperties())
{
sb.Append(property.Name);
sb.Append(": ");
sb.Append(this.GetType().GetProperty(property.Name).Name);
sb.Append(System.Environment.NewLine);
}
}
return sb.ToString();
}
}
This currently outputs:
Prop1: System.String Prop1
Prop2: System.String Prop2
Desired Output:
Prop1: blah1
Prop2: blah2
I'm open for other solutions, it doesn't have to use reflection, it just has to produce the desired output.
This works for me:
public class TestingClass
{
public string Prop1 { get; set; }//properties
public string Prop2 { get; set; }
public void Method1(string a) { }//method
public TestingClass() { }//const
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (System.Reflection.PropertyInfo property in this.GetType().GetProperties())
{
sb.Append(property.Name);
sb.Append(": ");
if (property.GetIndexParameters().Length > 0)
{
sb.Append("Indexed Property cannot be used");
}
else
{
sb.Append(property.GetValue(this, null));
}
sb.Append(System.Environment.NewLine);
}
return sb.ToString();
}
}
To make it available everywhere you can create an Extension.
It's not possible to override methods in an Extension, but still it should simplify your life.
public static class MyExtensions
{
public static string ToStringExtension(this object obj)
{
StringBuilder sb = new StringBuilder();
foreach (System.Reflection.PropertyInfo property in obj.GetType().GetProperties())
{
sb.Append(property.Name);
sb.Append(": ");
if (property.GetIndexParameters().Length > 0)
{
sb.Append("Indexed Property cannot be used");
}
else
{
sb.Append(property.GetValue(obj, null));
}
sb.Append(System.Environment.NewLine);
}
return sb.ToString();
}
}
You can then call ToStringExtension() on every object.
Downside is, it doesn't work perfectly for lists etc., example:
var list = new List<string>();
// (filling list ommitted)
list.ToStringExtension();
// output:
// Capacity: 16
// Count: 11
// Item: Indexed Property cannot be used
Here is an extension which will report the standard types such as string, int and Datetime but will also report string lists (shown below in AccessPoints which the above answer could not handle). Note that the output is aligned such as:
Name : Omegaman
ID : 1
Role : Admin
AccessPoints : Alpha, Beta, Gamma
WeekDays : Mon, Tue
StartDate : 3/18/2014 12:16:07 PM
Below is the extension which takes in any type as long as its a class. It then reflects off of the public and private properties and if they are not null reports them.
public static string ReportAllProperties<T>(this T instance) where T : class
{
if (instance == null)
return string.Empty;
var strListType = typeof(List<string>);
var strArrType = typeof(string[]);
var arrayTypes = new[] { strListType, strArrType };
var handledTypes = new[] { typeof(Int32), typeof(String), typeof(bool), typeof(DateTime), typeof(double), typeof(decimal), strListType, strArrType };
var validProperties = instance.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(prop => handledTypes.Contains(prop.PropertyType))
.Where(prop => prop.GetValue(instance, null) != null)
.ToList();
var format = string.Format("{{0,-{0}}} : {{1}}", validProperties.Max(prp => prp.Name.Length));
return string.Join(
Environment.NewLine,
validProperties.Select(prop => string.Format(format,
prop.Name,
(arrayTypes.Contains(prop.PropertyType) ? string.Join(", ", (IEnumerable<string>)prop.GetValue(instance, null))
: prop.GetValue(instance, null)))));
}
Usage
myInstance.ReportAllProperties()
Note that this is based off my blog article C#: ToString To Report all Properties Even Private Ones Via Reflection which provides a more robust explanation of what is going on.
I would use JSON, Serializer will do all the hard work for you:
public static class ObjectExtensions
{
public static string ToStringEx(this object obj)
{
return JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
}
}
This is what I found, that works with most compicated-types (including List):
public static string ToXml(object Obj, System.Type ObjType)
{
try
{
XmlSerializer ser;
XmlSerializerNamespaces SerializeObject = new mlSerializerNamespaces();
ser = new XmlSerializer((ObjType));
MemoryStream memStream;
memStream = new MemoryStream();
XmlTextWriter xmlWriter;
xmlWriter = new XmlTextWriter(memStream, Encoding.UTF8);
xmlWriter.Namespaces = true;
XmlQualifiedName[] qualiArrayXML = SerializeObject.ToArray();
ser.Serialize(xmlWriter, Obj);
xmlWriter.Close();
memStream.Close();
string xml;
xml = Encoding.UTF8.GetString(memStream.GetBuffer());
xml = xml.Substring(xml.IndexOf(Convert.ToChar(60)));
xml = xml.Substring(0, (xml.LastIndexOf(Convert.ToChar(62)) + 1));
return xml;
}
catch (Exception ex)
{ return string.Empty; }
}
usage:
string classAasString = ClassToXml.ToXml(a, typeof(ClassA)); //whare ClassA is an object
I ran into this myself where I am looking for an option to serialize into something readable. If there are no read only properties xml serialization can give a readable string. However if there are read only properties / fields then xml serialization is not an option.
public static string ToString(object serializeable)
{
var type = serializeable.GetType();
try
{
var sw = new StringWriter();
new XmlSerializer(type).Serialize(sw, serializeable);
return sw.ToString();
}
catch
{
return type.FullName;
}
}
So I wrote an extension method that calls a library that has already figured out all the voodoo.
"override string ToString()" vs (my) "ToStringDump"....
Before I show the code, the reason I like the extension method (ToStringDump in this case).. better, is that I don't have to riddle my POCO/DTO objects with ObjectDump references. I believe POCOs and DTOs should be "very very clean", and even isolated in their own assembly. This way, these poco/dto objects are easily shared.
public static class ObjectDumpAdapter
{
public static string ToStringDump(this object obj)
{
string returnValue = ObjectDumper.Dump(obj);
return returnValue;
}
}
My dotnet core csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ObjectDumper.NET" Version="2.5.20033.1" />
</ItemGroup>
</Project>
Nuget link:
https://www.nuget.org/packages/ObjectDumper.NET/
Quote:
ObjectDumper is a utility which aims to serialize C# objects to string
for debugging and logging purposes.
(from https://nugetmusthaves.com/Package/ObjectDumper.NET )
GitHub link:
https://github.com/thomasgalliker/ObjectDumper

XML Serialization and Schema without xsd.exe

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;
}

Categories