Introduction
AutoMapper is a very cool object-to-object mapping library that works great mapping POCO-to-POCO and even handles
collections pretty seamlessly. Unfortunately, in the real world, sometimes the data we need comes to us in other formats. This article explores mapping XML data
using AutoMapper.
Background
If you're like me, you found this article because you went searching for how to get AutoMapper to work with XML. If you did, you probably ran across the article by
Danny Douglass on his blog. While this was extremely
helpful, it only gets us part of the way there, as the source and target objects in this scenario are fairly simple. How would you handle more complicated XML data?
Perhaps with nested types, or nested collections of types? This article picks up where that one left off and shows how to quickly and easily build a mapping for
more complicated XML data.
At this point, some may be asking, "If it's XML data, why wouldn't you use XML serialization to solve the problem?" The answer, obviously, is that you could.
However, in some circumstances, using object mapping may be easier, or you may have other constraints and considerations why you might want to use object mapping. Plus, it was an interesting problem to solve.
Using the Code
For our sample program here, we're going to consume an XML file which defines a nested menu structure. This menu supports menu items, and submenus, where each submenu
in turn supports menu items and submenus. The file we're going to process looks like this:
="1.0"="utf-8"
<Menu>
<SubMenus>
<Menu name="SubMenu 1">
<SubMenus>
<Menu name="Sub-SubMenu 1">
<MenuItems>
<MenuItem name="Menu Item 1-1-1" action="Action1.exe" />
<MenuItem name="Menu Item 1-1-2" action="Action2.exe" parameters="-2" />
</MenuItems>
</Menu>
<Menu name="Sub-SubMenu 2">
<MenuItems>
<MenuItem name="Menu Item 1-2-1" action="Action3.exe" />
<MenuItem name="Menu Item 1-2-2" action="Action4.exe" parameters="-2" />
</MenuItems>
</Menu>
</SubMenus>
</Menu>
<Menu name="Sub Menu 2">
<MenuItems>
<MenuItem name="Menu Item 2-1" action="Action5.exe" parameters="-item 2.1" />
</MenuItems>
</Menu>
</SubMenus>
<MenuItems>
<MenuItem name="Menu Item 1" action="Action6.exe"/>
<MenuItem name="Menu Item 2" action="Action7.exe" parameters="-7" />
</MenuItems>
</Menu>
The classes for the types we need are pretty straight forward from a modeling standpoint.
public class Menu
{
public Menu()
{
SubMenus = new List<Menu>();
MenuItems = new List<MenuItem>();
}
public IList<Menu> SubMenus { get; set; }
public IList<MenuItem> MenuItems { get; set; }
public string Name { get; set; }
}
public class MenuItem
{
public string Name { get; set; }
public string Action { get; set; }
public string Parameters { get; set; }
}
At this point, AutoMapper needs to be told how to map the XML to these objects. At first, this might seem like it would be
a lot of coding, but it's surprisingly small. Well, at least it was to me. YMMV. In the post from
Danny Douglass,
he shows a custom XElement
resolver which he uses in his example. That's almost what's needed here, but since
the sample XML has data as attributes instead of elements, a custom XAttributeResolver
class is
needed instead. As you can see in the snippet below, the name of the attribute is included in the constructor so the resolver knows
which attribute to resolve.
public class XAttributeResolver<T> : ValueResolver<XElement, T>
{
public XAttributeResolver(string attributeName)
{
Name = attributeName;
}
public string Name { get; set; }
protected override T ResolveCore(XElement source)
{
if (source == null)
return default(T);
var attribute = source.Attribute(Name);
if (attribute == null || String.IsNullOrEmpty(attribute.Value))
return default(T);
return (T)Convert.ChangeType(attribute.Value, typeof(T));
}
}
With the custom resolver complete, the maps can be built. We'll create a static <code>
MapInitializer
class with a CreateMenuMap
method to contain the map construction logic. For the MenuItem
class, the mapping is relatively straight forward - simply map the
relevant attributes to the property names. The attribute name is passed to the XAttributeResolver
using the
ConstructedBy()
member of the Mapper
class.
Mapper.CreateMap<XElement, MenuItem>()
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("name")))
.ForMember(dest => dest.Action,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("action")))
.ForMember(dest => dest.Parameters,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("parameters")));
The Menu
class is a bit trickier, as in addition to the Name
property, there are nested Menu
and MenuItem
collections to deal with. Fortunately, given the structure of the XML data, it is possible to deal with both in a single delegate method. This method looks
through descendants of the current node for a collection node, which contains the sub-element nodes. The delegate, then (with a little bit of error-
prevention code), essentially flattens the source data and allows AutoMapper to walk the collection of XElement
like it would any other
collection.
private static Func<XElement, string,
string, List<XElement>> _mapItems =
(src, collectionName, elementName) =>
(src.Element(collectionName) ??
new XElement(collectionName)).Elements(elementName).ToList();
Armed now with this delegate, the Menu
map is fairly straight forward to create using the MapFrom
method.
Mapper.CreateMap<XElement, Menu>()
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("name")))
.ForMember(dest => dest.MenuItems,
opt => opt.MapFrom(src => _mapItems(src, "MenuItems", "MenuItem")))
.ForMember(dest => dest.SubMenus,
opt => opt.MapFrom(src => _mapItems(src, "SubMenus", "SubMenu")));
Putting it all together, the MapInitializer
class looks like this:
public static class MapInitializer
{
private static Func<XElement, string, string, List<XElement>> _mapItems =
(src, collectionName, elementName) =>
(src.Element(collectionName) ?? new XElement(collectionName)).Elements(elementName).ToList();
public static void CreateMenuMap()
{
Mapper.CreateMap<XElement, MenuItem>()
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("name")))
.ForMember(dest => dest.Action,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("action")))
.ForMember(dest => dest.Parameters,
opt => opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("parameters")));
Mapper.CreateMap<XElement, Menu>()
.ForMember(dest => dest.Name,
opt =>
opt.ResolveUsing<XAttributeResolver<string>>()
.ConstructedBy(() => new XAttributeResolver<string>("name")))
.ForMember(dest => dest.MenuItems,
opt => opt.MapFrom(src => _mapItems(src, "MenuItems", "MenuItem")))
.ForMember(dest => dest.SubMenus,
opt => opt.MapFrom(src => _mapItems(src, "SubMenus", "Menu")));
}
}
At this point, everything we need to parse the XML should be ready to go. In my first pass, I did the XDocument.Load()
and passed the results to Mapper.Map()
. Pretty simple, right?
MapInitializer.CreateMenuMap();
var xml = XDocument.Load(@".\Menu.xml");
var menu = Mapper.Map<XDocument, Menu>(xml);
Only one problem - the Menu
resolver needs an XElement
, not an XDocument
. Doh!
A quick tweak of the code:
var menu = Mapper.Map<XElement,>(xml.Element("Menu"));
and some magic handwaving, the voila! - AutoMapper parses the XML and we can display an output of the menu.
> SubMenu 1
> Sub-SubMenu 1
Menu Item 1-1-1
Menu Item 1-1-2
> Sub-SubMenu 2
Menu Item 1-2-1
Menu Item 1-2-2
> Sub Menu 2
Menu Item 2-1
Menu Item 1
Menu Item 2
Conclusion
Armed with the basics outlined in the article here, you should now have the tools you need to tackle almost any
complex XML documents with AutoMapper.