XML is still widely used in applications. My recent usage of XML comes mostly from REST services. REST service responses nowadays use JSON formatting for the most part, but XML is still used as well. Most services, I have ever dealt with, use camel casing for naming properties, that is, a property name has a name like customerName, and not CustomerName (with a capital C).
For my example, I have created a simple silly class with multiple property types. I will show how to serialize it using a mix of XML attributes and attribute overrides.
I have defined a few classes with a variety of member types. Notice that property allowance has an XmlElement attribute (WEEKLY_MONEY) attached to it. There are occasions when the serialization tag names have to be different than the property name. For these cases, you will need to add an attribute in code, just like I did with “Allowance”. Here is the code that defines the classes to be serialized:
- public enum Gender
- {
- Female, Male
- }
- public class Book
- {
- public string Title { get; set; }
- public string Color { get; set; }
- }
- public class MyCustomer
- {
- public string NameAndLastName { get; set; }
- public int Age { get; set; }
- public Double Height { get; set; }
- public Gender Gender { get; set; }
- public List<Book> ReadingPile { get; set; }
- public Boolean Active { get; set; }
- [XmlElement(ElementName = "WEEKLY_MONEY")]
- public Decimal Allowance { get; set; }
- public MyCustomer ReferredBy { get; set; }
- }
In order to serialize this object to XML, using camel casing and without polluting my code with repetitive attributes that in real life happens thousands of times (ad nauseum), I have used XML attribute overrides. These are not simple to use. I spent quite some time getting them to work. But patience pays off. Thousands of lines of code will be saved.
XML attribute overrides, by definition, will do just that: override any attribute present in a class or property. If the attribute is not present, it’s added anyway. But, I want the behavior to be the inverse of this, i.e., the overrides to be the normal behavior, and any attributes present in code to “override the override”. To accomplish this, I need to check for attributes for every processed property and class. This is done using reflection. Wherever I find an attribute in code, I do not apply the override.
Some notes on the simplicity of some of my code- my attribute detection is poor. I am just checking for attributes whose name starts with “Xml”. This is not real life code. Just to illustrate how a feature is used.
Finally, before moving on to the code, I will give a very brief explanation of how XML override attributes work. The minimum necessary knowledge to get the job done:
There are many classes in dot Net related to the topic of attribute overrides, but the 2 most important ones are: XmlAttributeOverrides and XmlAttributes (both plural). The first is “the bag” of definitions which I will send to the serializer. It tells the serializer “Hey, this is how I want you to serialize the classes I specify here”. For any class not in “the bag”, the serializer will apply the default behavior. The second object, (XmlAttributes) is a collection of attributes that must be included for each class, and for each property. XmlAttributes defines the behavior (whether a property is an XmlElement attribute, XmlRoot, or XmlArray with XmlArrayItem, etc.). So the rule is to create one XmlAttributes object for each class, and one for each property in the class. Once these are defined, they are all thrown into the bag (added to XmlAttributeOverrides) to be sent to the serializer.
The following code adds the attributes for all the properties in my classes using reflection. This code does not cover every scenario under the sun, so if you use it, most likely you will have to consider a few more cases to match your application needs, and apply more logic for handling exceptions and attribute detection.
This example,
- Adds an XmlRoot attribute to the outmost element
- Sets a default tag name which is the camel case representation of the property name.
- If a property has an Xml attribute attached to it, it will not change its serializing behavior (no overrides will be applied)
- Will wrap collection items (List and Array only) with a “COLLECTIONxxx” tag.
- Uses UPPER case in places with the intention of highlighting things in the serialized output. Just for ease of finding the important parts. Not the right formatting for a real life scenario.
The code uses some small “helper” methods, which I list here first. You may want to skip them and refer to them after you study the main code, if you feel you still need to check them. These helpers include a few simple string overrides as well.
HELPERS
- public static class Overrides
- {
- public static string ToPascal(this string s) { return ChangeCasing(s, Char.ToUpper); }
- public static string ToCamel(this string s) { return ChangeCasing(s, Char.ToLower); }
- private static string ChangeCasing(string s, Func<Char, Char> convert)
- {
- return string.IsNullOrWhiteSpace(s) ? s : string.Format("{0}{1}", convert(s[0]), s.Substring(1));
- }
- }
- private static HashSet<Type> GetTypesToOverride(Type objectType)
- {
- var returnValue = new HashSet<Type>();
- returnValue.Add(objectType);
- Type elementType = objectType.GetElementType();
- if (elementType != null)
- returnValue.UnionWith(GetTypesToOverride(elementType));
- objectType.GetGenericArguments()
- .Where(t => t != null)
- .ToList()
- .ForEach(t => returnValue.UnionWith(GetTypesToOverride(t)));
- returnValue.RemoveWhere(t => t == null || t.FullName.StartsWith("System"));
- return returnValue;
- }
- private static bool HasXmlAttributes(MemberInfo minfo)
- {
- List<Attribute> xmlAttributes = minfo.GetCustomAttributes()
- .Where(t => {
- string typeName = t.GetType().Name;
- return typeName.StartsWith("Xml") && !"XmlObjectWrapperAttribute".Equals(typeName);
- })
- .ToList();
- return xmlAttributes.Count > 0;
- }
CORE FUNCTIONALITY
Join the conversation! Your thoughts help the community grow.